Skip to content

NPE when client subscribes to a ticking table with a grouped column #6750

@kosak

Description

@kosak

I think a recent change introduced a bug and a null pointer exception in BarrageUtil.java

The bug is triggered by unreleased code in my branch for the C++ client, so it's slightly awkward to repro.

Now, you might think "whatever you're talking about, that is probably the client's fault". However, please consider:

  1. any NPE on the server side is a bug, regardless of what the client is doing
  2. I think I know what the problem is
  3. I have a change explains what I think the solution should look like

On the server I have a table created this way

from deephaven import time_table
t1 = time_table("PT0:00:0.5").select(["Mod2 = ii % 2", "II = ii"]).group_by("Mod2")

When I try to subscribe to it from the client, I see this stack trace on the server:

re-server-1  | 2025-03-31T03:09:15.693Z | heduler-Concurrent-2 | ERROR | i.d.s.s.SessionService    | Internal Error '080a7f7b-cb9d-4737-b93e-dd84941ae4f6' java.lang.NullPointerException: Cannot invoke "java.lang.Class.getComponentType()" because "componentType" is null
core-server-1  |        at io.deephaven.extensions.barrage.util.BarrageUtil.arrowFieldFor(BarrageUtil.java:1028)
core-server-1  |        at io.deephaven.extensions.barrage.util.BarrageUtil.arrowFieldFor(BarrageUtil.java:1027)
core-server-1  |        at io.deephaven.extensions.barrage.util.BarrageUtil.lambda$columnDefinitionsToFields$5(BarrageUtil.java:621)
core-server-1  |        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
core-server-1  |        at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
core-server-1  |        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
core-server-1  |        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
core-server-1  |        at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
core-server-1  |        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
core-server-1  |        at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
core-server-1  |        at io.deephaven.extensions.barrage.util.BarrageUtil.makeSchema(BarrageUtil.java:468)
core-server-1  |        at io.deephaven.extensions.barrage.util.BarrageUtil.makeTableSchemaPayload(BarrageUtil.java:453)
core-server-1  |        at io.deephaven.server.barrage.BarrageMessageProducer.lambda$propagateSnapshotForSubscription$15(BarrageMessageProducer.java:1683)
core-server-1  |        at io.deephaven.extensions.barrage.BarrageMessageWriterImpl$Factory.getSchemaView(BarrageMessageWriterImpl.java:92)
core-server-1  |        at io.deephaven.server.barrage.BarrageMessageProducer.propagateSnapshotForSubscription(BarrageMessageProducer.java:1682)
core-server-1  |        at io.deephaven.server.barrage.BarrageMessageProducer.updateSubscriptionsSnapshotAndPropagate(BarrageMessageProducer.java:1538)
core-server-1  |        at io.deephaven.server.barrage.BarrageMessageProducer$UpdatePropagationJob.run(BarrageMessageProducer.java:1072)
core-server-1  |        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
core-server-1  |        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
core-server-1  |        at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
core-server-1  |        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
core-server-1  |        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
core-server-1  |        at io.deephaven.server.runner.scheduler.SchedulerModule$ThreadFactory.lambda$newThread$0(SchedulerModule.java:100)
core-server-1  |        at org.jpy.PyLib.callAndReturnObject(Native Method)
core-server-1  |        at org.jpy.PyObject.call(PyObject.java:444)
core-server-1  |        at io.deephaven.server.console.python.DebuggingInitializer.lambda$createInitializer$0(DebuggingInitializer.java:46)
core-server-1  |        at java.base/java.lang.Thread.run(Thread.java:1583)

The offending code is in BarrageUtil.arrowFieldFor(), which recursively calls itself if the type is an array or a Vector. I'll copy the whole method here

public static Field arrowFieldFor(
       final String name,
       final Class<?> type,
       final Class<?> componentType,
       final Map<String, String> metadata,
       final boolean columnAsList) {
   List<Field> children = Collections.emptyList();


   final FieldType fieldType = arrowFieldTypeFor(type, metadata, columnAsList);
   if (fieldType.getType().isComplex()) {
       if (type.isArray() || Vector.class.isAssignableFrom(type)) {
           children = Collections.singletonList(arrowFieldFor(
                   "", componentType, componentType.getComponentType(), Collections.emptyMap(), false));
       } else {
           throw Exceptions.statusRuntimeException(Code.INVALID_ARGUMENT,
                   "No default mapping for Arrow complex type: " + fieldType.getType());
       }
   }


   return new Field(name, fieldType, children);
}

In the failing case, this method is getting an array of LongVector. So in the outer call, type=LongVector[] and componentType=LongVector

Where this goes wrong is when the method tries to make a recursive call. It calls itself recursively with componentType, componentType.getComponentType(). This feels OK until you note that Java's Type.getComponentType() returns null for any Type is not an array. It does not have any special support for Vector classes.

So, the nested call receives type=LongVector and componentType=null. This nested call wants to recurse again, but the call to componentType.getComponentType() fails with an NPE and here we are.

I think the correct fix is to handle the Vector case separately as in:

            if (type.isArray()) {
                children = Collections.singletonList(arrowFieldFor(
                        "", componentType, componentType.getComponentType(), Collections.emptyMap(), false));
            } else if (Vector.class.isAssignableFrom(type)) {
                return arrowFieldForVectorType(name, type, componentType, metadata);
            }  else ...

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingtriage

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions