15
15
import io .deephaven .internal .log .LoggerFactory ;
16
16
import io .deephaven .io .logger .Logger ;
17
17
import io .deephaven .proto .backplane .grpc .*;
18
+ import io .deephaven .proto .backplane .script .grpc .ConsoleServiceGrpc ;
18
19
import io .deephaven .proto .util .Exceptions ;
19
20
import io .deephaven .util .SafeCloseable ;
20
- import io .deephaven .util .function .ThrowingRunnable ;
21
21
import io .grpc .Context ;
22
22
import io .grpc .ForwardingServerCall .SimpleForwardingServerCall ;
23
23
import io .grpc .ForwardingServerCallListener ;
36
36
37
37
import javax .inject .Inject ;
38
38
import javax .inject .Singleton ;
39
+ import java .io .Closeable ;
39
40
import java .lang .Object ;
40
41
import java .util .LinkedHashMap ;
41
42
import java .util .Map ;
43
+ import java .util .Set ;
42
44
import java .util .UUID ;
43
45
44
46
public class SessionServiceGrpcImpl extends SessionServiceGrpc .SessionServiceImplBase {
@@ -310,10 +312,19 @@ private void addHeaders(final Metadata md) {
310
312
311
313
@ Singleton
312
314
public static class SessionServiceInterceptor implements ServerInterceptor {
315
+ private static final Status AUTHENTICATION_DETAILS_INVALID =
316
+ Status .UNAUTHENTICATED .withDescription ("Authentication details invalid" );
317
+
318
+ // We can't use just io.grpc.MethodDescriptor (unless we chose provide and inject the named method descriptors),
319
+ // some of our methods are overridden from stock gRPC; for example,
320
+ // io.deephaven.server.object.ObjectServiceGrpcBinding.bindService.
321
+ // The goal should be to migrate all of the existing RPC Session close management logic to here if possible.
322
+ private static final Set <String > CANCEL_RPC_ON_SESSION_CLOSE = Set .of (
323
+ ConsoleServiceGrpc .getSubscribeToLogsMethod ().getFullMethodName (),
324
+ ObjectServiceGrpc .getMessageStreamMethod ().getFullMethodName ());
325
+
313
326
private final SessionService service ;
314
327
private final SessionService .ErrorTransformer errorTransformer ;
315
- private static final Status authenticationDetailsInvalid =
316
- Status .UNAUTHENTICATED .withDescription ("Authentication details invalid" );
317
328
318
329
@ Inject
319
330
public SessionServiceInterceptor (
@@ -344,12 +355,8 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<Re
344
355
try {
345
356
session = service .getSessionForAuthToken (token );
346
357
} catch (AuthenticationException e ) {
347
- try {
348
- call .close (authenticationDetailsInvalid , new Metadata ());
349
- } catch (IllegalStateException ignored ) {
350
- // could be thrown if the call was already closed. As an interceptor, we can't throw,
351
- // so ignoring this and just returning the no-op listener.
352
- }
358
+ // As an interceptor, we can't throw, so ignoring this and just returning the no-op listener.
359
+ safeClose (call , AUTHENTICATION_DETAILS_INVALID , new Metadata (), false );
353
360
return new ServerCall .Listener <>() {};
354
361
}
355
362
}
@@ -363,33 +370,61 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(final ServerCall<Re
363
370
364
371
final MutableObject <SessionServiceCallListener <ReqT , RespT >> listener = new MutableObject <>();
365
372
rpcWrapper (serverCall , context , finalSession , errorTransformer , () -> listener .setValue (
366
- new SessionServiceCallListener <>(serverCallHandler .startCall (serverCall , metadata ), serverCall ,
367
- context , finalSession , errorTransformer )));
373
+ listener (serverCall , metadata , serverCallHandler , context , finalSession )));
368
374
if (listener .getValue () == null ) {
369
375
return new ServerCall .Listener <>() {};
370
376
}
371
377
return listener .getValue ();
372
378
}
379
+
380
+ private <ReqT , RespT > @ NotNull SessionServiceCallListener <ReqT , RespT > listener (
381
+ InterceptedCall <ReqT , RespT > serverCall ,
382
+ Metadata metadata ,
383
+ ServerCallHandler <ReqT , RespT > serverCallHandler ,
384
+ Context context ,
385
+ SessionState session ) {
386
+ return new SessionServiceCallListener <>(
387
+ serverCallHandler .startCall (serverCall , metadata ),
388
+ serverCall ,
389
+ context ,
390
+ session ,
391
+ errorTransformer ,
392
+ CANCEL_RPC_ON_SESSION_CLOSE .contains (serverCall .getMethodDescriptor ().getFullMethodName ()));
393
+ }
373
394
}
374
395
375
396
private static class SessionServiceCallListener <ReqT , RespT > extends
376
- ForwardingServerCallListener .SimpleForwardingServerCallListener <ReqT > {
397
+ ForwardingServerCallListener .SimpleForwardingServerCallListener <ReqT > implements Closeable {
398
+ private static final Status SESSION_CLOSED = Status .CANCELLED .withDescription ("Session closed" );
399
+
377
400
private final ServerCall <ReqT , RespT > call ;
378
401
private final Context context ;
379
402
private final SessionState session ;
380
403
private final SessionService .ErrorTransformer errorTransformer ;
404
+ private final boolean autoCancelOnSessionClose ;
381
405
382
- public SessionServiceCallListener (
406
+ SessionServiceCallListener (
383
407
ServerCall .Listener <ReqT > delegate ,
384
408
ServerCall <ReqT , RespT > call ,
385
409
Context context ,
386
410
SessionState session ,
387
- SessionService .ErrorTransformer errorTransformer ) {
411
+ SessionService .ErrorTransformer errorTransformer ,
412
+ boolean autoCancelOnSessionClose ) {
388
413
super (delegate );
389
414
this .call = call ;
390
415
this .context = context ;
391
416
this .session = session ;
392
417
this .errorTransformer = errorTransformer ;
418
+ this .autoCancelOnSessionClose = autoCancelOnSessionClose ;
419
+ if (autoCancelOnSessionClose && session != null ) {
420
+ session .addOnCloseCallback (this );
421
+ }
422
+ }
423
+
424
+ @ Override
425
+ public void close () {
426
+ // session.addOnCloseCallback
427
+ safeClose (call , SESSION_CLOSED , new Metadata (), false );
393
428
}
394
429
395
430
@ Override
@@ -405,11 +440,17 @@ public void onHalfClose() {
405
440
@ Override
406
441
public void onCancel () {
407
442
rpcWrapper (call , context , session , errorTransformer , super ::onCancel );
443
+ if (autoCancelOnSessionClose && session != null ) {
444
+ session .removeOnCloseCallback (this );
445
+ }
408
446
}
409
447
410
448
@ Override
411
449
public void onComplete () {
412
450
rpcWrapper (call , context , session , errorTransformer , super ::onComplete );
451
+ if (autoCancelOnSessionClose && session != null ) {
452
+ session .removeOnCloseCallback (this );
453
+ }
413
454
}
414
455
415
456
@ Override
@@ -432,34 +473,44 @@ private static <ReqT, RespT> void rpcWrapper(
432
473
@ NotNull final Context context ,
433
474
@ Nullable final SessionState session ,
434
475
@ NotNull final SessionService .ErrorTransformer errorTransformer ,
435
- @ NotNull final ThrowingRunnable < InterruptedException > lambda ) {
476
+ @ NotNull final Runnable lambda ) {
436
477
Context previous = context .attach ();
437
478
// note: we'll open the execution context here so that it may be used by the error transformer
438
479
try (final SafeCloseable ignored1 = session == null ? null : session .getExecutionContext ().open ()) {
439
480
try (final SafeCloseable ignored2 = LivenessScopeStack .open ()) {
440
481
lambda .run ();
441
- } catch (final InterruptedException err ) {
442
- Thread .currentThread ().interrupt ();
443
- closeWithError (call , errorTransformer .transform (err ));
444
- } catch (final Throwable err ) {
445
- closeWithError (call , errorTransformer .transform (err ));
482
+ } catch (final RuntimeException err ) {
483
+ safeClose (call , errorTransformer .transform (err ));
484
+ } catch (final Error error ) {
485
+ // Indicates a very serious failure; debateable whether we should even try to send close.
486
+ safeClose (call , Status .INTERNAL , new Metadata (), false );
487
+ throw error ;
446
488
} finally {
447
489
context .detach (previous );
448
490
}
449
491
}
450
492
}
451
493
452
- private static < ReqT , RespT > void closeWithError (
453
- @ NotNull final ServerCall <ReqT , RespT > call ,
494
+ private static void safeClose (
495
+ @ NotNull final ServerCall <?, ? > call ,
454
496
@ NotNull final StatusRuntimeException err ) {
497
+ Metadata metadata = Status .trailersFromThrowable (err );
498
+ if (metadata == null ) {
499
+ metadata = new Metadata ();
500
+ }
501
+ safeClose (call , Status .fromThrowable (err ), metadata , true );
502
+ }
503
+
504
+ private static void safeClose (ServerCall <?, ?> call , Status status , Metadata trailers , boolean logOnError ) {
455
505
try {
456
- Metadata metadata = Status .trailersFromThrowable (err );
457
- if (metadata == null ) {
458
- metadata = new Metadata ();
506
+ call .close (status , trailers );
507
+ } catch (IllegalStateException e ) {
508
+ // IllegalStateException is explicitly documented as thrown if the call is already closed. It might be nice
509
+ // if there was a more explicit exception type, but this should suffice. We _could_ try and check the text
510
+ // "call already closed", but that is an undocumented implementation detail we should probably not rely on.
511
+ if (logOnError && log .isDebugEnabled ()) {
512
+ log .debug ().append ("call.close error: " ).append (e ).endl ();
459
513
}
460
- call .close (Status .fromThrowable (err ), metadata );
461
- } catch (final Exception unexpectedErr ) {
462
- log .debug ().append ("Unanticipated gRPC Error: " ).append (unexpectedErr ).endl ();
463
514
}
464
515
}
465
516
}
0 commit comments