Vinod KC created SPARK-58094:
--------------------------------

             Summary: Blocking RPCs hang forever when the connection dies 
mid-wait (no gRPC keepalive / no client-side deadline)
                 Key: SPARK-58094
                 URL: https://issues.apache.org/jira/browse/SPARK-58094
             Project: Spark
          Issue Type: Bug
          Components: Connect
    Affects Versions: 4.2.0, 4.3.0
            Reporter: Vinod KC
         Attachments: spark-connect-hang-demo.zip

Any blocking call in Spark Connect can hang indefinitely and never throw if the 
network path between client and server goes silently dark while the RPC is 
waiting on a response (e.g. a NAT gateway, load balancer, or corporate proxy 
evicting an idle connection without sending a TCP RST/FIN). This is a 
transport-layer gap, not specific to any one API: Spark Connect configures no 
gRPC/HTTP2 keepalive on either side, and the client's blocking read path has no 
deadline by default. Any RPC whose response is delayed while its connection is 
silently dropped is exposed — most visibly `StreamingQuery.awaitTermination()` 
and other long-lived/idle-prone calls (long streaming queries, session status 
polling, gaps between reattach calls), since they hold a connection open and 
idle for long enough that a middlebox is likely to evict it — but the same 
failure mode applies to a regular (non-streaming) query's RPC if its connection 
happens to be silently dropped mid-wait; only the size of the exposure window 
differs, not the mechanism.

*Impact:*

A Mission Critical case surfaced via StreamingQuery.awaitTermination(): a 
streaming job stalled for  hours after a single non-retryable source error.

Symptoms:
- StreamingQueryListener.onQueryTerminated() fired correctly with the terminal 
exception.
- The client's blocked query.awaitTermination() call never returned and never 
threw.
- The job run stayed "RUNNING" indefinitely; the driver process was healthy and 
idle the whole time (no CPU/network activity, no errors logged).

 

*Root Cause:*

Two independent gaps combine to produce the hang, both at the transport/channel 
level shared by all RPCs:
1. No gRPC/HTTP2 keepalive is configured on either the client channel 
(SparkConnectClient.scala) or the server (SparkConnectService.scala's 
NettyServerBuilder), so a silently-dropped connection is never detected.
2. The client's blocking read (ExecutePlanResponseReattachableIterator → 
ArrayBlockingQueue.take()) has no deadline, and the server's polling loops 
(e.g. handleStreamingAwaitTermination) only exit via 
Context.current.isCancelled, a signal that itself depends on the transport 
noticing the dead connection — which never happens without keepalive.

*Issue repro:*

Reproduced with two separate JVM processes (real Connect server + separate 
client), using a `frozen TCP proxy` to simulate a silently-dropped connection 
(no root/iptables needed).

Confirmed via thread dump that the client is parked forever in 
ArrayBlockingQueue.take() while the server-side query has already terminated 
independently. awaitTermination() was used as the concrete repro vehicle 
because its long idle/blocking window makes the bug easy and reliable to 
trigger, not because the bug is scoped to it.

 

Issue repro setup in zip file  `spark-connect-hang-demo.zip`



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to