Skip to content

Commit 0053e15

Browse files
PenguinToastgatorsmile
authored andcommitted
[SPARK-24337][CORE] Improve error messages for Spark conf values
## What changes were proposed in this pull request? Improve the exception messages when retrieving Spark conf values to include the key name when the value is invalid. ## How was this patch tested? Unit tests for all get* operations in SparkConf that require a specific value format Author: William Sheu <[email protected]> Closes #21454 from PenguinToast/SPARK-24337-spark-config-errors.
1 parent 24ef7fb commit 0053e15

File tree

2 files changed

+96
-21
lines changed

2 files changed

+96
-21
lines changed

core/src/main/scala/org/apache/spark/SparkConf.scala

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -265,108 +265,121 @@ class SparkConf(loadDefaults: Boolean) extends Cloneable with Logging with Seria
265265
* Get a time parameter as seconds; throws a NoSuchElementException if it's not set. If no
266266
* suffix is provided then seconds are assumed.
267267
* @throws java.util.NoSuchElementException If the time parameter is not set
268+
* @throws NumberFormatException If the value cannot be interpreted as seconds
268269
*/
269-
def getTimeAsSeconds(key: String): Long = {
270+
def getTimeAsSeconds(key: String): Long = catchIllegalValue(key) {
270271
Utils.timeStringAsSeconds(get(key))
271272
}
272273

273274
/**
274275
* Get a time parameter as seconds, falling back to a default if not set. If no
275276
* suffix is provided then seconds are assumed.
277+
* @throws NumberFormatException If the value cannot be interpreted as seconds
276278
*/
277-
def getTimeAsSeconds(key: String, defaultValue: String): Long = {
279+
def getTimeAsSeconds(key: String, defaultValue: String): Long = catchIllegalValue(key) {
278280
Utils.timeStringAsSeconds(get(key, defaultValue))
279281
}
280282

281283
/**
282284
* Get a time parameter as milliseconds; throws a NoSuchElementException if it's not set. If no
283285
* suffix is provided then milliseconds are assumed.
284286
* @throws java.util.NoSuchElementException If the time parameter is not set
287+
* @throws NumberFormatException If the value cannot be interpreted as milliseconds
285288
*/
286-
def getTimeAsMs(key: String): Long = {
289+
def getTimeAsMs(key: String): Long = catchIllegalValue(key) {
287290
Utils.timeStringAsMs(get(key))
288291
}
289292

290293
/**
291294
* Get a time parameter as milliseconds, falling back to a default if not set. If no
292295
* suffix is provided then milliseconds are assumed.
296+
* @throws NumberFormatException If the value cannot be interpreted as milliseconds
293297
*/
294-
def getTimeAsMs(key: String, defaultValue: String): Long = {
298+
def getTimeAsMs(key: String, defaultValue: String): Long = catchIllegalValue(key) {
295299
Utils.timeStringAsMs(get(key, defaultValue))
296300
}
297301

298302
/**
299303
* Get a size parameter as bytes; throws a NoSuchElementException if it's not set. If no
300304
* suffix is provided then bytes are assumed.
301305
* @throws java.util.NoSuchElementException If the size parameter is not set
306+
* @throws NumberFormatException If the value cannot be interpreted as bytes
302307
*/
303-
def getSizeAsBytes(key: String): Long = {
308+
def getSizeAsBytes(key: String): Long = catchIllegalValue(key) {
304309
Utils.byteStringAsBytes(get(key))
305310
}
306311

307312
/**
308313
* Get a size parameter as bytes, falling back to a default if not set. If no
309314
* suffix is provided then bytes are assumed.
315+
* @throws NumberFormatException If the value cannot be interpreted as bytes
310316
*/
311-
def getSizeAsBytes(key: String, defaultValue: String): Long = {
317+
def getSizeAsBytes(key: String, defaultValue: String): Long = catchIllegalValue(key) {
312318
Utils.byteStringAsBytes(get(key, defaultValue))
313319
}
314320

315321
/**
316322
* Get a size parameter as bytes, falling back to a default if not set.
323+
* @throws NumberFormatException If the value cannot be interpreted as bytes
317324
*/
318-
def getSizeAsBytes(key: String, defaultValue: Long): Long = {
325+
def getSizeAsBytes(key: String, defaultValue: Long): Long = catchIllegalValue(key) {
319326
Utils.byteStringAsBytes(get(key, defaultValue + "B"))
320327
}
321328

322329
/**
323330
* Get a size parameter as Kibibytes; throws a NoSuchElementException if it's not set. If no
324331
* suffix is provided then Kibibytes are assumed.
325332
* @throws java.util.NoSuchElementException If the size parameter is not set
333+
* @throws NumberFormatException If the value cannot be interpreted as Kibibytes
326334
*/
327-
def getSizeAsKb(key: String): Long = {
335+
def getSizeAsKb(key: String): Long = catchIllegalValue(key) {
328336
Utils.byteStringAsKb(get(key))
329337
}
330338

331339
/**
332340
* Get a size parameter as Kibibytes, falling back to a default if not set. If no
333341
* suffix is provided then Kibibytes are assumed.
342+
* @throws NumberFormatException If the value cannot be interpreted as Kibibytes
334343
*/
335-
def getSizeAsKb(key: String, defaultValue: String): Long = {
344+
def getSizeAsKb(key: String, defaultValue: String): Long = catchIllegalValue(key) {
336345
Utils.byteStringAsKb(get(key, defaultValue))
337346
}
338347

339348
/**
340349
* Get a size parameter as Mebibytes; throws a NoSuchElementException if it's not set. If no
341350
* suffix is provided then Mebibytes are assumed.
342351
* @throws java.util.NoSuchElementException If the size parameter is not set
352+
* @throws NumberFormatException If the value cannot be interpreted as Mebibytes
343353
*/
344-
def getSizeAsMb(key: String): Long = {
354+
def getSizeAsMb(key: String): Long = catchIllegalValue(key) {
345355
Utils.byteStringAsMb(get(key))
346356
}
347357

348358
/**
349359
* Get a size parameter as Mebibytes, falling back to a default if not set. If no
350360
* suffix is provided then Mebibytes are assumed.
361+
* @throws NumberFormatException If the value cannot be interpreted as Mebibytes
351362
*/
352-
def getSizeAsMb(key: String, defaultValue: String): Long = {
363+
def getSizeAsMb(key: String, defaultValue: String): Long = catchIllegalValue(key) {
353364
Utils.byteStringAsMb(get(key, defaultValue))
354365
}
355366

356367
/**
357368
* Get a size parameter as Gibibytes; throws a NoSuchElementException if it's not set. If no
358369
* suffix is provided then Gibibytes are assumed.
359370
* @throws java.util.NoSuchElementException If the size parameter is not set
371+
* @throws NumberFormatException If the value cannot be interpreted as Gibibytes
360372
*/
361-
def getSizeAsGb(key: String): Long = {
373+
def getSizeAsGb(key: String): Long = catchIllegalValue(key) {
362374
Utils.byteStringAsGb(get(key))
363375
}
364376

365377
/**
366378
* Get a size parameter as Gibibytes, falling back to a default if not set. If no
367379
* suffix is provided then Gibibytes are assumed.
380+
* @throws NumberFormatException If the value cannot be interpreted as Gibibytes
368381
*/
369-
def getSizeAsGb(key: String, defaultValue: String): Long = {
382+
def getSizeAsGb(key: String, defaultValue: String): Long = catchIllegalValue(key) {
370383
Utils.byteStringAsGb(get(key, defaultValue))
371384
}
372385

@@ -394,23 +407,35 @@ class SparkConf(loadDefaults: Boolean) extends Cloneable with Logging with Seria
394407
}
395408

396409

397-
/** Get a parameter as an integer, falling back to a default if not set */
398-
def getInt(key: String, defaultValue: Int): Int = {
410+
/**
411+
* Get a parameter as an integer, falling back to a default if not set
412+
* @throws NumberFormatException If the value cannot be interpreted as an integer
413+
*/
414+
def getInt(key: String, defaultValue: Int): Int = catchIllegalValue(key) {
399415
getOption(key).map(_.toInt).getOrElse(defaultValue)
400416
}
401417

402-
/** Get a parameter as a long, falling back to a default if not set */
403-
def getLong(key: String, defaultValue: Long): Long = {
418+
/**
419+
* Get a parameter as a long, falling back to a default if not set
420+
* @throws NumberFormatException If the value cannot be interpreted as a long
421+
*/
422+
def getLong(key: String, defaultValue: Long): Long = catchIllegalValue(key) {
404423
getOption(key).map(_.toLong).getOrElse(defaultValue)
405424
}
406425

407-
/** Get a parameter as a double, falling back to a default if not set */
408-
def getDouble(key: String, defaultValue: Double): Double = {
426+
/**
427+
* Get a parameter as a double, falling back to a default if not ste
428+
* @throws NumberFormatException If the value cannot be interpreted as a double
429+
*/
430+
def getDouble(key: String, defaultValue: Double): Double = catchIllegalValue(key) {
409431
getOption(key).map(_.toDouble).getOrElse(defaultValue)
410432
}
411433

412-
/** Get a parameter as a boolean, falling back to a default if not set */
413-
def getBoolean(key: String, defaultValue: Boolean): Boolean = {
434+
/**
435+
* Get a parameter as a boolean, falling back to a default if not set
436+
* @throws IllegalArgumentException If the value cannot be interpreted as a boolean
437+
*/
438+
def getBoolean(key: String, defaultValue: Boolean): Boolean = catchIllegalValue(key) {
414439
getOption(key).map(_.toBoolean).getOrElse(defaultValue)
415440
}
416441

@@ -448,6 +473,24 @@ class SparkConf(loadDefaults: Boolean) extends Cloneable with Logging with Seria
448473
*/
449474
private[spark] def getenv(name: String): String = System.getenv(name)
450475

476+
/**
477+
* Wrapper method for get() methods which require some specific value format. This catches
478+
* any [[NumberFormatException]] or [[IllegalArgumentException]] and re-raises it with the
479+
* incorrectly configured key in the exception message.
480+
*/
481+
private def catchIllegalValue[T](key: String)(getValue: => T): T = {
482+
try {
483+
getValue
484+
} catch {
485+
case e: NumberFormatException =>
486+
// NumberFormatException doesn't have a constructor that takes a cause for some reason.
487+
throw new NumberFormatException(s"Illegal value for config key $key: ${e.getMessage}")
488+
.initCause(e)
489+
case e: IllegalArgumentException =>
490+
throw new IllegalArgumentException(s"Illegal value for config key $key: ${e.getMessage}", e)
491+
}
492+
}
493+
451494
/**
452495
* Checks for illegal or deprecated config settings. Throws an exception for the former. Not
453496
* idempotent - may mutate this conf object to convert deprecated settings to supported ones.

core/src/test/scala/org/apache/spark/SparkConfSuite.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,38 @@ class SparkConfSuite extends SparkFunSuite with LocalSparkContext with ResetSyst
339339
}
340340
}
341341

342+
val defaultIllegalValue = "SomeIllegalValue"
343+
val illegalValueTests : Map[String, (SparkConf, String) => Any] = Map(
344+
"getTimeAsSeconds" -> (_.getTimeAsSeconds(_)),
345+
"getTimeAsSeconds with default" -> (_.getTimeAsSeconds(_, defaultIllegalValue)),
346+
"getTimeAsMs" -> (_.getTimeAsMs(_)),
347+
"getTimeAsMs with default" -> (_.getTimeAsMs(_, defaultIllegalValue)),
348+
"getSizeAsBytes" -> (_.getSizeAsBytes(_)),
349+
"getSizeAsBytes with default string" -> (_.getSizeAsBytes(_, defaultIllegalValue)),
350+
"getSizeAsBytes with default long" -> (_.getSizeAsBytes(_, 0L)),
351+
"getSizeAsKb" -> (_.getSizeAsKb(_)),
352+
"getSizeAsKb with default" -> (_.getSizeAsKb(_, defaultIllegalValue)),
353+
"getSizeAsMb" -> (_.getSizeAsMb(_)),
354+
"getSizeAsMb with default" -> (_.getSizeAsMb(_, defaultIllegalValue)),
355+
"getSizeAsGb" -> (_.getSizeAsGb(_)),
356+
"getSizeAsGb with default" -> (_.getSizeAsGb(_, defaultIllegalValue)),
357+
"getInt" -> (_.getInt(_, 0)),
358+
"getLong" -> (_.getLong(_, 0L)),
359+
"getDouble" -> (_.getDouble(_, 0.0)),
360+
"getBoolean" -> (_.getBoolean(_, false))
361+
)
362+
363+
illegalValueTests.foreach { case (name, getValue) =>
364+
test(s"SPARK-24337: $name throws an useful error message with key name") {
365+
val key = "SomeKey"
366+
val conf = new SparkConf()
367+
conf.set(key, "SomeInvalidValue")
368+
val thrown = intercept[IllegalArgumentException] {
369+
getValue(conf, key)
370+
}
371+
assert(thrown.getMessage.contains(key))
372+
}
373+
}
342374
}
343375

344376
class Class1 {}

0 commit comments

Comments
 (0)