Skip to content

Commit 4e9aeea

Browse files
authored
Improve test coverage for streaming exceptions (#61)
* [COVERAGE] Improve test coverage for streaming exceptions * [BUGFIX] Ensure response streams close channels on exception (even when blocking)
1 parent 62edc62 commit 4e9aeea

3 files changed

Lines changed: 105 additions & 11 deletions

File tree

src/Curl.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function write_callback(
4242
GRPC_INTERNAL,
4343
"Recieved $(n) bytes from curl but only handled $(handled_n_bytes_total)",
4444
)
45+
!isnothing(req.response_c) && close(req.response_c)
4546
return typemax(Csize_t)
4647
end
4748

@@ -351,13 +352,15 @@ function handle_write(req::gRPCRequest, buf::Vector{UInt8})::Tuple{Int64, Union{
351352
GRPC_UNIMPLEMENTED,
352353
"Response was compressed but compression is not currently supported.",
353354
)
355+
!isnothing(req.response_c) && close(req.response_c)
354356
notify(req.ready)
355357
return n, nothing
356358
elseif req.response_length > req.max_recieve_message_length
357359
req.ex = gRPCServiceCallException(
358360
GRPC_RESOURCE_EXHAUSTED,
359361
"length-prefix longer than max_recieve_message_length: $(req.response_length) > $(req.max_recieve_message_length)",
360362
)
363+
!isnothing(req.response_c) && close(req.response_c)
361364
notify(req.ready)
362365
return n, nothing
363366
end

src/Streaming.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ function grpc_async_stream_request(
5757
end
5858
end
5959
catch ex
60-
close(channel)
61-
close(req.request_c)
62-
6360
if isa(ex, InvalidStateException)
6461
# Wait for any request data to be flushed by curl
6562
wait(req.curl_done_reading)
@@ -82,6 +79,9 @@ function grpc_async_stream_request(
8279
end
8380
@error "grpc_async_stream_request: unexpected exception" exception = ex
8481
end
82+
finally
83+
close(channel)
84+
close(req.request_c)
8585
end
8686

8787
nothing
@@ -98,9 +98,6 @@ function grpc_async_stream_response(
9898
put!(channel, response)
9999
end
100100
catch ex
101-
close(channel)
102-
close(req.response_c)
103-
104101
if isa(ex, InvalidStateException)
105102

106103
elseif isa(ex, gRPCServiceCallException)
@@ -115,7 +112,9 @@ function grpc_async_stream_response(
115112
end
116113
@error "grpc_async_stream_response: unexpected exception" exception = ex
117114
end
118-
115+
finally
116+
close(channel)
117+
close(req.response_c)
119118
end
120119

121120
nothing

test/runtests.jl

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,18 +239,110 @@ include("gen/test/test_pb.jl")
239239
@test length(response.data) == N
240240
# end
241241

242-
# @testset "Don't Stick User Tasks"
242+
# @testset "Don't Stick User Tasks"
243243
# This fails on Julia 1.10 but works on Julia 1.12
244244
client = TestService_TestRPC_Client(_TEST_HOST, _TEST_PORT)
245245

246-
task = @sync begin
247-
@spawn begin
246+
task = @sync begin
247+
@spawn begin
248248
grpc_sync_request(client, TestRequest(1, zeros(UInt64, 1)))
249249
end
250-
end
250+
end
251251

252252
@test !task.sticky
253253
# end
254+
255+
# @testset "grpc_async_stream_request - gRPCServiceCallException" begin
256+
# Test that gRPCServiceCallException is properly stored in req.ex
257+
client = TestService_TestClientStreamRPC_Client(_TEST_HOST, _TEST_PORT; max_send_message_length=100)
258+
request_c = Channel{TestRequest}(1)
259+
260+
req = grpc_async_request(client, request_c)
261+
262+
# Send a request that exceeds max_send_message_length to trigger gRPCServiceCallException
263+
put!(request_c, TestRequest(1, zeros(UInt64, 1000)))
264+
close(request_c)
265+
266+
# Wait and check that the exception is a gRPCServiceCallException
267+
try
268+
grpc_async_await(client, req)
269+
@test false # Should not reach here
270+
catch ex
271+
@test isa(ex, gRPCServiceCallException)
272+
end
273+
# end
274+
275+
# @testset "grpc_async_stream_request - general exception" begin
276+
# Test the else branch with a non-gRPC exception
277+
client = TestService_TestClientStreamRPC_Client(_TEST_HOST, _TEST_PORT)
278+
request_c = Channel{TestRequest}(1)
279+
280+
req = grpc_async_request(client, request_c)
281+
282+
# Close the channel and then try to take from it (triggers InvalidStateException)
283+
close(request_c)
284+
285+
# Give the async task time to encounter the exception
286+
sleep(0.2)
287+
288+
# The InvalidStateException should be handled gracefully
289+
# and the request should complete (possibly with no error or a different error)
290+
try
291+
grpc_async_await(client, req)
292+
catch ex
293+
# If there's an exception, it shouldn't be InvalidStateException
294+
# (that should be handled internally)
295+
@test !isa(ex, InvalidStateException)
296+
end
297+
# end
298+
299+
# @testset "grpc_async_stream_response - InvalidStateException" begin
300+
# Test that InvalidStateException is handled when response channel closes early
301+
client = TestService_TestServerStreamRPC_Client(_TEST_HOST, _TEST_PORT)
302+
response_c = Channel{TestResponse}(1)
303+
304+
req = grpc_async_request(client, TestRequest(10, zeros(UInt64, 1)), response_c)
305+
306+
# Take one response then close the channel to trigger InvalidStateException in put!
307+
response = take!(response_c)
308+
@test length(response.data) >= 1
309+
close(response_c)
310+
311+
# Give time for the async task to encounter InvalidStateException
312+
sleep(0.2)
313+
314+
# InvalidStateException should be handled internally without propagating
315+
try
316+
grpc_async_await(req)
317+
catch ex
318+
# If there's an exception, it shouldn't be InvalidStateException
319+
@test !isa(ex, InvalidStateException)
320+
end
321+
# end
322+
323+
# @testset "grpc_async_stream_response - gRPCServiceCallException" begin
324+
# Test that gRPCServiceCallException is properly handled in response stream
325+
# Use a client with restrictive max_recieve_message_length
326+
client = TestService_TestServerStreamRPC_Client(_TEST_HOST, _TEST_PORT; max_recieve_message_length=1)
327+
response_c = Channel{TestResponse}(100)
328+
329+
# Request a response that will exceed the max size
330+
req = grpc_async_request(client, TestRequest(10, zeros(UInt64, 100)), response_c)
331+
332+
# Wait for the error to occur
333+
sleep(0.2)
334+
335+
# Should get gRPCServiceCallException when awaiting
336+
try
337+
for response in response_c
338+
# Might get some responses before the error
339+
end
340+
grpc_async_await(req)
341+
@test false # Should not reach here
342+
catch ex
343+
@test isa(ex, gRPCServiceCallException)
344+
end
345+
# end
254346
end
255347

256348
grpc_shutdown()

0 commit comments

Comments
 (0)