Skip to content

[BUG] get_json_object retry path races malformed and following rows #5079

Description

@gerashegalov

Describe the bug

get_json_object can silently corrupt a valid row when an earlier valid row expands during JSON normalization and an intervening malformed row writes partial output.

This reproduces with a CUDA 13 JNI artifact on an RTX PRO 4500 Blackwell (SM120). The problem is in the JNI out-of-bound retry path and does not depend on CUDA 12 compatibility.

The three-row reproducer below returns corrupted JSON for row 2. In 1,000 iterations, all 1,000 produced {"bi:"b""} instead of {"big":""}.

Steps/Code to reproduce bug

Run against a CUDA 13 spark-rapids-jni jar:

import ai.rapids.cudf.ColumnVector;
import com.nvidia.spark.rapids.jni.JSONUtils;

public final class GetJsonObjectRetryRace {
  public static void main(String[] args) {
    var rootPath = new JSONUtils.PathInstructionJni[0];
    try (var input = ColumnVector.fromStrings(
             "{\"a\":\"\n\"}",   // Output expands, activating the retry path.
             "{\"a\":\"b\"c\"}", // Malformed after writing partial output.
             "{\"big\": \"\"}"); // Valid victim row.
         var output = JSONUtils.getJsonObject(input, rootPath);
         var host = output.copyToHost()) {
      System.out.println(host.getJavaString(2));
    }
  }
}

Expected:

{"big":""}

Observed on every iteration in a 1,000-iteration run:

{"bi:"b""}

The same failure was originally exposed by the existing cudf-spark integration test get_json_test.py::test_get_json_object_spark_unit_tests[$]. Its 3,000-character final value is not required; the small victim above is sufficient.

Expected behavior

The malformed row should be null and must not modify any other row. The final valid row should be returned as {"big":""}.

Environment details (please complete the following information)

  • Environment location: Standalone/local
  • GPU: NVIDIA RTX PRO 4500 Blackwell, compute capability 12.0
  • Driver: 595.71.05
  • CUDA artifact: CUDA 13
  • spark-rapids-jni revision: ecb9d76acc5e0ea36fd4f5c5e1955067d05ec607
  • cuDF revision: 5ebcdf692e6cbf3470eb0cf0634c200611828e62
  • Spark configuration settings related to the issue: None; the reproducer calls the JNI API directly without Spark

Additional context

The apparent race is in the out-of-bound retry path:

  1. Normalizing the literal newline in row 0 expands its output and marks the path for retry.
  2. evaluate_path writes partial output for malformed row 1, but returns size zero because the row is invalid.
  3. Retry offsets are built from the first-pass sizes. Row 1 therefore has a zero-width interval, and row 2 receives the same destination address.
  4. The second kernel launch reruns both rows directly into the packed output buffer. The malformed row still writes partial/trash bytes, racing row 2 at the aliased address.

The corrupt bytes support this explanation: the malformed row writes a and : at the same positions where the original integration test changes {"big":... into values such as {"ai:":... or {"bi:":....

A standalone control that passed equivalent pointer/size pairs through both cudf::make_strings_column and cudf::make_strings_column_batch passed 10,000/10,000 iterations on the same GPU. That places the defect before cuDF string-column construction.

The retry implementation was introduced in 05b05a07 and remains structurally unchanged on public main at e37b31fab90d5a86c4985b88279d801ecb6bb6db apart from later API migrations.

Related but not a duplicate: #4889 tracks deterministic Spark semantic differences and a separate one-byte out-of-bounds read. This report is a cross-row output race that corrupts otherwise valid JSON.

Blackwell is the architecture on which this was reproduced. Other GPU architectures have not yet been tested with the minimized case. The destination alias exists in the code independent of architecture, while whether corruption becomes visible may be scheduling-dependent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions