You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ol...@apache.org on 2026/10/05 17:30:04 UTC
(daffodil) branch main updated: Wait for a computable target length before unparsing truncating strings
This is an automated email from the ASF dual-hosted git repository.
olabusayoT pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/main by this push:
new d25d1d73f Wait for a computable target length before unparsing truncating strings
d25d1d73f is described below
commit d25d1d73f533d1c793e3ac46e9cd1d9985826f33
Author: olabusayoT <50...@users.noreply.github.com>
AuthorDate: Tue Sep 29 17:59:41 2026 -0400
Wait for a computable target length before unparsing truncating strings
A string with dfdl:truncateSpecifiedLengthString whose dfdl:length is an expression reading an outputValueCalc element failed with an unretried InfosetNoDataException. The truncating string unparsers evaluate the target length inline, while the outputValueCalc was still blocked on a following sibling, so the error escaped the suspension machinery instead of being retried once the outputValueCalc could resolve.
Make the truncating string unparsers SuspendableUnparsers. Their new StringTruncationSuspendableOperation tests that the target length can be evaluated and then runs the string unparse as its continuation.
truncatedString_2 now unparses. truncatedString_1 and _4 depend on dfdl:valueLength and remain true deadlocks; their expected diagnostic is now the circular deadlock error.
DAFFODIL-1598
---
.../unparsers/runtime1/StringLengthUnparsers.scala | 51 ++++++++++++++++++++--
.../section00/general/testUnparserGeneral.tdml | 10 ++---
2 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala
index 49e1b31af..0b2be365b 100644
--- a/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala
+++ b/daffodil-core/src/main/scala/org/apache/daffodil/unparsers/runtime1/StringLengthUnparsers.scala
@@ -26,6 +26,7 @@ import org.apache.daffodil.runtime1.processors.CharsetEv
import org.apache.daffodil.runtime1.processors.ElementRuntimeData
import org.apache.daffodil.runtime1.processors.Evaluatable
import org.apache.daffodil.runtime1.processors.LengthEv
+import org.apache.daffodil.runtime1.processors.SuspendableOperation
import org.apache.daffodil.runtime1.processors.TextTruncationType
import org.apache.daffodil.runtime1.processors.UnparseTargetLengthInBitsEv
import org.apache.daffodil.runtime1.processors.unparsers.*
@@ -70,10 +71,26 @@ class StringNoTruncateUnparser(erd: ElementRuntimeData)
sealed abstract class StringSpecifiedLengthUnparserTruncateBase(
stringTruncationType: TextTruncationType.Type,
erd: ElementRuntimeData
-) extends StringSpecifiedLengthUnparserBase(erd) {
+) extends StringSpecifiedLengthUnparserBase(erd)
+ with SuspendableUnparser {
Assert.usage(stringTruncationType ne TextTruncationType.None)
+ /**
+ * The target length the string is truncated to. It can depend on an
+ * outputValueCalc element that has not been computed yet.
+ */
+ protected[runtime1] def targetLengthEv: Evaluatable[AnyRef]
+
+ /**
+ * Reads the target length without retrying, so it must only be called
+ * once the target length can be evaluated.
+ */
+ protected[runtime1] def unparseString(state: UState): Unit
+
+ override protected def suspendableOperation =
+ new StringTruncationSuspendableOperation(erd, this)
+
/**
* We only truncate strings, and only if textStringJustification is left or
* right, and only if truncateSpecifiedLengthString is yes.
@@ -122,6 +139,8 @@ class StringMaybeTruncateBitsUnparser(
override val runtimeDependencies = Array(targetLengthInBitsEv, charsetEv)
+ override protected[runtime1] def targetLengthEv = targetLengthInBitsEv
+
private def getLengthInBits(str: String, state: UState): (Long, Long) = {
val cs = charsetEv.evaluate(state)
val mfw = cs.maybeFixedWidth
@@ -171,7 +190,7 @@ class StringMaybeTruncateBitsUnparser(
res
}
- override def unparse(state: UState): Unit = {
+ override protected[runtime1] def unparseString(state: UState): Unit = {
//
// We have to stage the bits of the value just so as to be able to count them
@@ -259,7 +278,9 @@ class StringMaybeTruncateCharactersUnparser(
override val runtimeDependencies = Array(lengthInCharactersEv)
- override def unparse(state: UState): Unit = {
+ override protected[runtime1] def targetLengthEv = lengthInCharactersEv
+
+ override protected[runtime1] def unparseString(state: UState): Unit = {
val dos = state.getDataOutputStream
val valueString = contentString(state)
val targetLengthInCharacters =
@@ -285,3 +306,27 @@ class StringMaybeTruncateCharactersUnparser(
Assert.invariant(nCharsWritten == valueToWrite.length)
}
}
+
+/**
+ * Suspends until the target length can be evaluated, then unparses the
+ * string, because the truncating string unparsers read the target length
+ * without retrying.
+ */
+class StringTruncationSuspendableOperation(
+ override val rd: ElementRuntimeData,
+ unparser: StringSpecifiedLengthUnparserTruncateBase
+) extends SuspendableOperation {
+
+ override def toString = "string truncation for " + rd.diagnosticDebugName
+
+ // Evaluating throws a RetryableException while the length is unavailable,
+ // which blocks this operation.
+ override protected def test(ustate: UState): Boolean = {
+ unparser.targetLengthEv.evaluate(ustate)
+ true
+ }
+
+ override protected def continuation(ustate: UState): Unit = {
+ unparser.unparseString(ustate)
+ }
+}
diff --git a/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml b/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml
index 549cef279..4940801eb 100644
--- a/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml
+++ b/daffodil-test/src/test/resources/org/apache/daffodil/section00/general/testUnparserGeneral.tdml
@@ -1023,8 +1023,7 @@
</tdml:dfdlInfoset>
</tdml:infoset>
<tdml:errors>
- <tdml:error>not have a value</tdml:error>
- <tdml:error>circular dependency</tdml:error>
+ <tdml:error>circularly deadlocked</tdml:error>
</tdml:errors>
</tdml:unparserTestCase>
@@ -1055,10 +1054,7 @@
</ex:e1>
</tdml:dfdlInfoset>
</tdml:infoset>
- <tdml:errors>
- <tdml:error>not have a value</tdml:error>
- <tdml:error>circular dependency</tdml:error>
- </tdml:errors>
+ <tdml:document>5ABCDE</tdml:document>
</tdml:unparserTestCase>
<tdml:defineSchema name="truncatedString3">
@@ -1113,7 +1109,7 @@
</tdml:dfdlInfoset>
</tdml:infoset>
<tdml:errors>
- <tdml:error>Value length unknown</tdml:error>
+ <tdml:error>circularly deadlocked</tdml:error>
</tdml:errors>
</tdml:unparserTestCase>