@@ -237,5 +237,239 @@ def service.throw_method
237237 end
238238 end
239239 end
240+
241+ with "connection termination during transaction" do
242+ it "demonstrates unhandled RuntimeError when server crashes while client waits for response" do
243+ # This test demonstrates that when a server connection is closed while
244+ # a transaction is in-flight, the server task tries to write Return/Error
245+ # but the transaction is already closed, causing an unhandled RuntimeError.
246+ #
247+ # Expected behavior: When the remote end closes the connection mid-transaction,
248+ # the local end should fail with an exception, but that exception should be
249+ # handled gracefully (not left as an unhandled exception in an async task).
250+ server_connection = nil
251+ server_started = Thread ::Queue . new
252+ close_connection = Thread ::Queue . new
253+
254+ start_server do |connection |
255+ server_connection = connection
256+ service = Object . new
257+ def service . slow_method
258+ # Signal that server has started processing
259+ @server_started . push ( :started )
260+ # Wait for signal to close connection
261+ @close_connection . pop
262+ :result
263+ end
264+
265+ # Store queue references in service
266+ service . instance_variable_set ( :@server_started , server_started )
267+ service . instance_variable_set ( :@close_connection , close_connection )
268+
269+ connection . bind ( :service , service )
270+ end
271+
272+ client . connect do |connection |
273+ # Start the invocation
274+ invoke_task = Async do
275+ connection [ :service ] . slow_method
276+ end
277+
278+ # Wait for server to start processing
279+ server_started . pop
280+
281+ # Abruptly close the server connection
282+ server_connection . close
283+
284+ # Signal server to continue (it will try to write Return/Error)
285+ close_connection . push ( :close )
286+
287+ # Wait for processing to complete
288+ begin
289+ invoke_task . wait
290+ rescue
291+ # Expected to fail
292+ end
293+
294+ # Currently, this causes unhandled RuntimeError: "Transaction is closed!"
295+ # in the server task when it tries to write Return/Error after connection closes.
296+ # The test passes but demonstrates the issue: exceptions should be caught and
297+ # handled gracefully, not left as unhandled exceptions in async tasks.
298+ end
299+ end
300+
301+ it "demonstrates unhandled RuntimeError when client crashes while server executes method" do
302+ # This test demonstrates that when a client connection is closed while
303+ # a server transaction is executing, the server task tries to write Return/Error
304+ # but the transaction is already closed, causing an unhandled RuntimeError.
305+ #
306+ # Expected behavior: When the remote end closes the connection mid-transaction,
307+ # the local end should fail with an exception, but that exception should be
308+ # handled gracefully (not left as an unhandled exception in an async task).
309+ client_connection = nil
310+ server_started = Thread ::Queue . new
311+ close_connection = Thread ::Queue . new
312+
313+ start_server do |connection |
314+ service = Object . new
315+ def service . slow_method
316+ # Signal that server has started processing
317+ @server_started . push ( :started )
318+ # Wait for signal to close connection
319+ @close_connection . pop
320+ :result
321+ end
322+
323+ # Store queue references in service
324+ service . instance_variable_set ( :@server_started , server_started )
325+ service . instance_variable_set ( :@close_connection , close_connection )
326+
327+ connection . bind ( :service , service )
328+ end
329+
330+ client . connect do |connection |
331+ client_connection = connection
332+
333+ # Start the invocation
334+ invoke_task = Async do
335+ connection [ :service ] . slow_method
336+ end
337+
338+ # Wait for server to start processing
339+ server_started . pop
340+
341+ # Abruptly close the client connection
342+ client_connection . close
343+
344+ # Signal server to continue (it will try to write Return/Error)
345+ close_connection . push ( :close )
346+
347+ # Wait for processing to complete
348+ begin
349+ invoke_task . wait
350+ rescue
351+ # Expected to fail
352+ end
353+
354+ # Currently, this causes unhandled RuntimeError: "Transaction is closed!"
355+ # in the server task when it tries to write Return/Error after connection closes.
356+ # The test passes but demonstrates the issue: exceptions should be caught and
357+ # handled gracefully, not left as unhandled exceptions in async tasks.
358+ end
359+ end
360+
361+ it "demonstrates unhandled RuntimeError when server crashes during yield operation" do
362+ # This test demonstrates that when a server connection is closed during
363+ # a yield operation, the server task tries to write Yield but the transaction
364+ # is already closed, causing an unhandled RuntimeError.
365+ #
366+ # Expected behavior: When the remote end closes the connection mid-transaction,
367+ # the local end should fail with an exception, but that exception should be
368+ # handled gracefully (not left as an unhandled exception in an async task).
369+ server_connection = nil
370+ first_yield_received = Thread ::Queue . new
371+
372+ start_server do |connection |
373+ server_connection = connection
374+ service = Object . new
375+ def service . yielding_method
376+ yield 1
377+ # Wait for connection to be closed
378+ @first_yield_received . pop
379+ # This yield will fail because connection closes
380+ yield 2
381+ :done
382+ end
383+
384+ # Store queue reference in service
385+ service . instance_variable_set ( :@first_yield_received , first_yield_received )
386+
387+ connection . bind ( :service , service )
388+ end
389+
390+ client . connect do |connection |
391+ # Start the invocation with yield
392+ invoke_task = Async do
393+ results = [ ]
394+ connection [ :service ] . yielding_method do |value |
395+ results << value
396+
397+ # Close server connection after first yield
398+ if results . size == 1
399+ server_connection . close
400+ first_yield_received . push ( :received )
401+ end
402+
403+ :ack
404+ end
405+ end
406+
407+ # Wait for processing to complete
408+ begin
409+ invoke_task . wait
410+ rescue
411+ # Expected to fail
412+ end
413+
414+ # Currently, this causes unhandled RuntimeError: "Transaction is closed!"
415+ # in the server task when it tries to write Yield after connection closes.
416+ # The test passes but demonstrates the issue: exceptions should be caught and
417+ # handled gracefully, not left as unhandled exceptions in async tasks.
418+ end
419+ end
420+
421+ it "handles client crash during server yield operation" do
422+ client_connection = nil
423+ first_yield_received = Thread ::Queue . new
424+
425+ start_server do |connection |
426+ service = Object . new
427+ def service . yielding_method
428+ yield 1
429+ # Wait for connection to be closed
430+ @first_yield_received . pop
431+ # This yield will fail because client closes
432+ yield 2
433+ :done
434+ end
435+
436+ # Store queue reference in service
437+ service . instance_variable_set ( :@first_yield_received , first_yield_received )
438+
439+ connection . bind ( :service , service )
440+ end
441+
442+ client . connect do |connection |
443+ client_connection = connection
444+
445+ # Start the invocation with yield
446+ invoke_task = Async do
447+ results = [ ]
448+ connection [ :service ] . yielding_method do |value |
449+ results << value
450+
451+ # Close client connection after first yield
452+ if results . size == 1
453+ client_connection . close
454+ first_yield_received . push ( :received )
455+ end
456+
457+ :ack
458+ end
459+ end
460+
461+ # Wait for processing to complete
462+ begin
463+ invoke_task . wait
464+ rescue
465+ # Expected to fail
466+ end
467+
468+ # When the remote end closes the connection mid-transaction, the local end
469+ # should fail with an exception, but that exception should be handled gracefully
470+ # (not left as an unhandled exception in an async task).
471+ end
472+ end
473+ end
240474end
241475
0 commit comments