22
22
import io .netty .channel .ChannelHandlerContext ;
23
23
import io .netty .channel .ChannelOption ;
24
24
import io .netty .handler .codec .LengthFieldBasedFrameDecoder ;
25
+ import io .netty .util .ReferenceCountUtil ;
25
26
import io .r2dbc .postgresql .message .backend .BackendKeyData ;
26
27
import io .r2dbc .postgresql .message .backend .BackendMessage ;
27
28
import io .r2dbc .postgresql .message .backend .BackendMessageDecoder ;
34
35
import io .r2dbc .postgresql .message .frontend .FrontendMessage ;
35
36
import io .r2dbc .postgresql .message .frontend .Terminate ;
36
37
import io .r2dbc .postgresql .util .Assert ;
38
+ import io .r2dbc .spi .R2dbcNonTransientResourceException ;
37
39
import org .reactivestreams .Publisher ;
38
40
import org .slf4j .Logger ;
39
41
import org .slf4j .LoggerFactory ;
59
61
import java .util .StringJoiner ;
60
62
import java .util .concurrent .atomic .AtomicBoolean ;
61
63
import java .util .concurrent .atomic .AtomicInteger ;
64
+ import java .util .concurrent .atomic .AtomicReference ;
62
65
import java .util .function .Consumer ;
63
66
import java .util .function .Function ;
67
+ import java .util .function .Supplier ;
64
68
65
69
import static io .r2dbc .postgresql .client .TransactionStatus .IDLE ;
66
70
@@ -75,6 +79,10 @@ public final class ReactorNettyClient implements Client {
75
79
76
80
private static final boolean DEBUG_ENABLED = logger .isDebugEnabled ();
77
81
82
+ private static final Supplier <PostgresConnectionClosedException > UNEXPECTED = () -> new PostgresConnectionClosedException ("Connection unexpectedly closed" );
83
+
84
+ private static final Supplier <PostgresConnectionClosedException > EXPECTED = () -> new PostgresConnectionClosedException ("Connection closed" );
85
+
78
86
private final ByteBufAllocator byteBufAllocator ;
79
87
80
88
private final Connection connection ;
@@ -107,26 +115,36 @@ private ReactorNettyClient(Connection connection) {
107
115
Assert .requireNonNull (connection , "Connection must not be null" );
108
116
109
117
connection .addHandler (new LengthFieldBasedFrameDecoder (Integer .MAX_VALUE - 5 , 1 , 4 , -4 , 0 ));
110
- connection .addHandler (new EnsureSubscribersCompleteChannelHandler (this .requestProcessor , this . responseReceivers ));
118
+ connection .addHandler (new EnsureSubscribersCompleteChannelHandler (this .requestProcessor ));
111
119
this .connection = connection ;
112
120
this .byteBufAllocator = connection .outbound ().alloc ();
113
121
122
+ AtomicReference <Throwable > receiveError = new AtomicReference <>();
114
123
Mono <Void > receive = connection .inbound ().receive ()
115
124
.map (BackendMessageDecoder ::decode )
116
125
.handle (this ::handleResponse )
126
+ .doOnError (throwable -> {
127
+ receiveError .set (throwable );
128
+ handleConnectionError (throwable );
129
+ })
117
130
.windowWhile (it -> it .getClass () != ReadyForQuery .class )
118
131
.doOnNext (fluxOfMessages -> {
119
132
MonoSink <Flux <BackendMessage >> receiver = this .responseReceivers .poll ();
120
133
if (receiver != null ) {
121
- receiver .success (fluxOfMessages );
122
- }
123
- })
124
- .doOnComplete (() -> {
125
- MonoSink <Flux <BackendMessage >> receiver = this .responseReceivers .poll ();
126
- if (receiver != null ) {
127
- receiver .success (Flux .empty ());
134
+ receiver .success (fluxOfMessages .doOnComplete (() -> {
135
+
136
+ Throwable throwable = receiveError .get ();
137
+ if (throwable != null ) {
138
+ throw new PostgresConnectionException (throwable );
139
+ }
140
+
141
+ if (!isConnected ()) {
142
+ throw EXPECTED .get ();
143
+ }
144
+ }));
128
145
}
129
146
})
147
+ .doOnComplete (this ::handleClose )
130
148
.then ();
131
149
132
150
Mono <Void > request = this .requestProcessor
@@ -139,35 +157,27 @@ private ReactorNettyClient(Connection connection) {
139
157
.then ();
140
158
141
159
receive
142
- .onErrorResume (throwable -> {
143
-
144
- MonoSink <Flux <BackendMessage >> receiver = this .responseReceivers .poll ();
145
- if (receiver != null ) {
146
- receiver .error (throwable );
147
- }
148
- this .requestProcessor .onComplete ();
149
-
150
- if (isSslException (throwable )) {
151
- logger .debug ("Connection Error" , throwable );
152
- } else {
153
-
154
- logger .error ("Connection Error" , throwable );
155
- }
156
- return close ();
157
- })
160
+ .onErrorResume (this ::resumeError )
158
161
.subscribe ();
159
162
160
163
request
161
- .onErrorResume (throwable -> {
164
+ .onErrorResume (this ::resumeError )
165
+ .doAfterTerminate (this ::handleClose )
166
+ .subscribe ();
167
+ }
162
168
163
- if (isSslException (throwable )) {
164
- logger .debug ("Connection Error" , throwable );
165
- }
169
+ private Mono <Void > resumeError (Throwable throwable ) {
166
170
167
- logger .error ("Connection Error" , throwable );
168
- return close ();
169
- })
170
- .subscribe ();
171
+ handleConnectionError (throwable );
172
+ this .requestProcessor .onComplete ();
173
+
174
+ if (isSslException (throwable )) {
175
+ logger .debug ("Connection Error" , throwable );
176
+ } else {
177
+ logger .error ("Connection Error" , throwable );
178
+ }
179
+
180
+ return close ();
171
181
}
172
182
173
183
private static boolean isSslException (Throwable throwable ) {
@@ -301,9 +311,10 @@ private static Mono<? extends Void> registerSslHandler(SSLConfig sslConfig, Conn
301
311
public Mono <Void > close () {
302
312
return Mono .defer (() -> {
303
313
314
+ drainError (EXPECTED );
304
315
if (this .isClosed .compareAndSet (false , true )) {
305
316
306
- if (!this . connection . channel (). isOpen () || this .processId == null ) {
317
+ if (!isConnected () || this .processId == null ) {
307
318
this .connection .dispose ();
308
319
return this .connection .onDispose ();
309
320
}
@@ -326,24 +337,33 @@ public Flux<BackendMessage> exchange(Publisher<FrontendMessage> requests) {
326
337
327
338
return Mono
328
339
.<Flux <BackendMessage >>create (sink -> {
329
- if (this .isClosed .get ()) {
330
- sink .error (new IllegalStateException ("Cannot exchange messages because the connection is closed" ));
331
- }
332
340
333
341
final AtomicInteger once = new AtomicInteger ();
334
342
335
343
Flux .from (requests )
336
344
.subscribe (message -> {
345
+
346
+ if (!isConnected ()) {
347
+ ReferenceCountUtil .safeRelease (message );
348
+ sink .error (new PostgresConnectionClosedException ("Cannot exchange messages because the connection is closed" ));
349
+ return ;
350
+ }
351
+
337
352
if (once .get () == 0 && once .compareAndSet (0 , 1 )) {
338
353
synchronized (this ) {
339
354
this .responseReceivers .add (sink );
340
355
this .requests .next (message );
341
356
}
342
- return ;
357
+ } else {
358
+ this .requests .next (message );
343
359
}
344
360
345
- this .requests .next (message );
346
- }, this .requests ::error );
361
+ }, this .requests ::error , () -> {
362
+
363
+ if (!isConnected ()) {
364
+ sink .error (new PostgresConnectionClosedException ("Cannot exchange messages because the connection is closed" ));
365
+ }
366
+ });
347
367
348
368
})
349
369
.flatMapMany (Function .identity ());
@@ -380,6 +400,10 @@ public boolean isConnected() {
380
400
return false ;
381
401
}
382
402
403
+ if (this .requestProcessor .isDisposed ()) {
404
+ return false ;
405
+ }
406
+
383
407
Channel channel = this .connection .channel ();
384
408
return channel .isOpen ();
385
409
}
@@ -399,26 +423,59 @@ private static String toString(List<Field> fields) {
399
423
return joiner .toString ();
400
424
}
401
425
402
- private static final class EnsureSubscribersCompleteChannelHandler extends ChannelDuplexHandler {
426
+ private void handleClose () {
427
+ if (this .isClosed .compareAndSet (false , true )) {
428
+ drainError (UNEXPECTED );
429
+ } else {
430
+ drainError (EXPECTED );
431
+ }
432
+ }
403
433
404
- private final EmitterProcessor <FrontendMessage > requestProcessor ;
434
+ private void handleConnectionError (Throwable error ) {
435
+ drainError (() -> new PostgresConnectionException (error ));
436
+ }
405
437
406
- private final Queue <MonoSink <Flux <BackendMessage >>> responseReceivers ;
438
+ private void drainError (Supplier <? extends Throwable > supplier ) {
439
+ MonoSink <Flux <BackendMessage >> receiver ;
440
+
441
+ while ((receiver = this .responseReceivers .poll ()) != null ) {
442
+ receiver .error (supplier .get ());
443
+ }
444
+ }
407
445
408
- private EnsureSubscribersCompleteChannelHandler (EmitterProcessor <FrontendMessage > requestProcessor , Queue <MonoSink <Flux <BackendMessage >>> responseReceivers ) {
446
+ private final class EnsureSubscribersCompleteChannelHandler extends ChannelDuplexHandler {
447
+
448
+ private final EmitterProcessor <FrontendMessage > requestProcessor ;
449
+
450
+ private EnsureSubscribersCompleteChannelHandler (EmitterProcessor <FrontendMessage > requestProcessor ) {
409
451
this .requestProcessor = requestProcessor ;
410
- this .responseReceivers = responseReceivers ;
452
+ }
453
+
454
+ @ Override
455
+ public void channelInactive (ChannelHandlerContext ctx ) throws Exception {
456
+ super .channelInactive (ctx );
411
457
}
412
458
413
459
@ Override
414
460
public void channelUnregistered (ChannelHandlerContext ctx ) throws Exception {
415
461
super .channelUnregistered (ctx );
416
462
417
463
this .requestProcessor .onComplete ();
464
+ handleClose ();
465
+ }
466
+ }
418
467
419
- for (MonoSink <Flux <BackendMessage >> responseReceiver = this .responseReceivers .poll (); responseReceiver != null ; responseReceiver = this .responseReceivers .poll ()) {
420
- responseReceiver .success (Flux .empty ());
421
- }
468
+ static class PostgresConnectionClosedException extends R2dbcNonTransientResourceException {
469
+
470
+ public PostgresConnectionClosedException (String reason ) {
471
+ super (reason );
472
+ }
473
+ }
474
+
475
+ static class PostgresConnectionException extends R2dbcNonTransientResourceException {
476
+
477
+ public PostgresConnectionException (Throwable cause ) {
478
+ super (cause );
422
479
}
423
480
}
424
481
0 commit comments