Copilot commented on code in PR #12549:
URL: https://github.com/apache/gluten/pull/12549#discussion_r3868066030


##########
gluten-core/src/main/scala/org/apache/gluten/config/ConfigBuilder.scala:
##########
@@ -77,6 +82,152 @@ private[gluten] case class ConfigBuilder(key: String) {
     this
   }
 
+  /** Marks this config as a static (non-session-mutable) conf. Set by 
`buildStaticConf`. */
+  private[config] def markStatic(): ConfigBuilder = {
+    _isStatic = true
+    this
+  }
+
+  /**
+   * Marks this config as owned by Spark / Hadoop rather than by Gluten. Set 
by `registerConf` /
+   * `registerStaticConf`.
+   *
+   * A foreign config is not registered as a Gluten config entry and not 
registered to `SQLConf`
+   * (Spark or Hadoop already did that); the builder is only used to declare 
how the key is
+   * delivered to native side.
+   */
+  private[config] def markForeign(): ConfigBuilder = {
+    _isForeign = true
+    this
+  }
+
+  /**
+   * Marks this foreign config as delivering the default declared by Spark / 
Hadoop for the key,
+   * resolved freshly at each delivery. Set by `createWithForeignDefault`, 
which is only meaningful
+   * for a foreign config: a Gluten config states its default in 
`createWithDefault(value)` instead.
+   */
+  private[config] def markDeliverForeignDefault(): ConfigBuilder = {
+    require(
+      _isForeign,
+      s"Config $key: createWithForeignDefault is only valid for registerConf() 
/ " +
+        s"registerStaticConf(), since a Gluten config has no Spark / Hadoop 
declaration to " +
+        s"resolve a default from. Use createWithDefault(value) instead."
+    )
+    _deliverForeignDefault = true
+    this
+  }
+
+  /**
+   * Marks this config to be passed to native side. A value set by the user is 
always delivered;
+   * what happens when it is not set is stated by the terminal method, which 
is the whole of the
+   * rule:
+   *
+   *   - `createOptional`: nothing is delivered, leaving native's own fallback 
in charge. This is
+   *     the common case for a foreign key, since native usually declares the 
same fallback Spark /
+   *     Hadoop does, or branches on the key being absent at all.
+   *   - `createWithForeignDefault` (foreign only): the default Spark / Hadoop 
declares for the key
+   *     is delivered, resolved freshly at each delivery. Use it when native's 
fallback is wrong or
+   *     missing, and the foreign default is either computed at runtime
+   *     (`spark.sql.session.timeZone` follows the JVM default time zone) or 
has changed across
+   *     Spark versions (`spark.sql.ansi.enabled` flipped its default in 4.0). 
Never restate such a
+   *     default on the Gluten side - that is exactly what drifts.
+   *   - `createWithDefault(value)`: the stated value is delivered. For a 
Gluten config this is its
+   *     own default; for a foreign one it says Gluten deliberately departs 
from what both Spark /
+   *     Hadoop and native would apply, e.g. `fs.s3a.path.style.access` where 
native falls back to
+   *     `false` and Gluten wants `true`.
+   *
+   * The config is registered to [[NativeConfRegistry]] on entry creation.
+   *
+   * Which delivery channels it lands on follows the conf's mutability, so 
there is no argument:
+   *   - `buildConf` / `registerConf`: modifiable at any time and usable at 
any time. Delivered both
+   *     during native backend initialization and on each native runtime 
creation, so native
+   *     observes the current value wherever it reads the key.
+   *   - `buildStaticConf` / `registerStaticConf`: set while the native 
backend is initialized and
+   *     not modifiable afterwards. Delivered once during native backend 
initialization.
+   */
+  def passToNative(): ConfigBuilder = {
+    _passToNative = true
+    this
+  }
+
+  /**
+   * Normalizes a value for native side through the conf's own value 
converter, i.e. the one chosen
+   * by `stringConf` / `bytesConf(unit)` / `intConf` / ... plus any 
`transform`. A conf therefore
+   * states how its value is parsed exactly once, at its declaration, and both 
a user-set value and
+   * a resolved default go through it.
+   *
+   * This is what makes a size conf reach native as a number rather than as 
"64k": a
+   * `bytesConf(ByteUnit.KiB)` yields the KiB count the foreign entry would 
yield, and a
+   * `bytesConf(ByteUnit.BYTE)` the byte count. A foreign conf declares the 
same converter Spark /
+   * Hadoop declares, so JVM and native agree on the value's meaning and 
native applies whatever
+   * unit conversion it needs on top - `spark.shuffle.file.buffer` is KiB on 
both sides, and native
+   * multiplies by 1024.
+   *
+   * Falls back to the raw string when the entry has no usable converter (e.g. 
a fallback entry),
+   * since delivering the value unchanged is always better than dropping it.
+   */
+  private def convertForNative(entry: ConfigEntry[_], raw: String): String = {
+    try {
+      entry.valueConverter(raw) match {
+        // An `OptionalConfigEntry` wraps its converter's result in `Option`.
+        case o: Option[_] => o.map(_.toString).getOrElse(raw)
+        case null => raw
+        case v => v.toString
+      }
+    } catch {
+      // A value Spark or Hadoop would reject is not this mechanism's business 
to validate: Spark or
+      // Hadoop raises on it at its own read site, with its own message. 
Deliver it unchanged rather
+      // than failing conf selection, which runs per task.
+      case _: IllegalArgumentException => raw
+    }
+  }
+
+  private[config] def registerToNative(entry: ConfigEntry[_]): Unit = {
+    require(
+      !_isForeign || _passToNative,
+      s"Config $key: a config declared by registerConf() / 
registerStaticConf() must be marked " +
+        s"with passToNative(), otherwise declaring it has no effect"
+    )
+    if (!_passToNative) {
+      return
+    }
+    // The channel follows the conf's mutability. A modifiable conf is 
delivered on both channels so
+    // native observes the current value wherever it reads the key; a static 
conf is set while the
+    // native backend is initialized and not modifiable afterwards, so 
delivering it once there is
+    // lossless.
+    NativeConfRegistry.register(
+      key,
+      _isStatic,
+      convert = convertForNative(entry, _),
+      declaredDefault = declaredDefault(entry))
+  }
+
+  /**
+   * The default delivered to native for a key the user did not set, or `None` 
to deliver nothing
+   * and leave native's own fallback in charge. Read per delivery rather than 
snapshotted, so a
+   * default that follows JVM or session state keeps delivering its current 
value.
+   *
+   * Which of the three the caller gets is stated by the terminal method - see 
[[passToNative]].
+   */
+  private def declaredDefault(entry: ConfigEntry[_]): Option[String] = entry 
match {
+    // A fallback entry reports the *target* conf's default as its own, and 
the target is delivered
+    // under its own key. Delivering it here would also contradict the user: 
with only the target
+    // conf set, this key would carry the target's default rather than the 
value the user chose.
+    case _: ConfigEntryFallback[_] | _: ConfigEntryForeignFallback[_] => None
+    // `createWithForeignDefault` on a foreign key: take the foreign 
declaration, resolved
+    // now rather than restated on the Gluten side, so the two cannot drift 
across
+    // versions. The foreign default is a raw string ("32k" for
+    // `spark.shuffle.file.buffer`), so it goes through this conf's own 
converter just
+    // as a user-set value does.
+    case e if _deliverForeignDefault =>
+      
GlutenConfigUtil.resolveForeignDeclaredDefault(key).map(convertForNative(e, _))
+    // `createWithDefault(value)`. Reading the parsed default rather than the 
raw
+    // default string means e.g. a "64MB" bytes conf reaches native as 
"67108864".
+    case e if e.defaultValue.isDefined => e.defaultValue.map(_.toString)
+    // `createOptional`: nothing is delivered when the key is not set.
+    case _ => None

Review Comment:
   `declaredDefault` calls `e.defaultValue` twice (`isDefined` in the guard, 
then again in the body). For `ConfigEntryWithDefaultFunction` this re-evaluates 
the dynamic default multiple times per delivery, which can be inconsistent (if 
the default depends on mutable JVM state) and adds unnecessary overhead. Prefer 
evaluating `defaultValue` only once (or just `map` it directly, which calls it 
once).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to