12
12
import java .util .Map ;
13
13
import java .util .concurrent .CompletableFuture ;
14
14
import java .util .concurrent .atomic .AtomicLong ;
15
+ import java .util .concurrent .atomic .AtomicReference ;
15
16
import java .util .stream .Collectors ;
16
17
import net .snowflake .ingest .streaming .InsertValidationResponse ;
17
18
import net .snowflake .ingest .streaming .OpenChannelRequest ;
@@ -45,7 +46,7 @@ class SnowflakeStreamingIngestChannelInternal implements SnowflakeStreamingInges
45
46
private volatile boolean isValid ;
46
47
47
48
// Indicates whether the channel is closed
48
- private volatile boolean isClosed ;
49
+ private final AtomicReference < Boolean > isClosed ;
49
50
50
51
// Reference to the client that owns this channel
51
52
private final SnowflakeStreamingIngestClientInternal owningClient ;
@@ -65,6 +66,8 @@ class SnowflakeStreamingIngestChannelInternal implements SnowflakeStreamingInges
65
66
// ON_ERROR option for this channel
66
67
private final OpenChannelRequest .OnErrorOption onErrorOption ;
67
68
69
+ private final CompletableFuture <Void > terminationFuture ;
70
+
68
71
/**
69
72
* Constructor for TESTING ONLY which allows us to set the test mode
70
73
*
@@ -99,7 +102,7 @@ class SnowflakeStreamingIngestChannelInternal implements SnowflakeStreamingInges
99
102
this .channelSequencer = channelSequencer ;
100
103
this .rowSequencer = new AtomicLong (rowSequencer );
101
104
this .isValid = true ;
102
- this .isClosed = false ;
105
+ this .isClosed = new AtomicReference <>( false ) ;
103
106
this .owningClient = client ;
104
107
this .isTestMode = isTestMode ;
105
108
this .allocator =
@@ -112,6 +115,8 @@ class SnowflakeStreamingIngestChannelInternal implements SnowflakeStreamingInges
112
115
this .encryptionKey = encryptionKey ;
113
116
this .encryptionKeyId = encryptionKeyId ;
114
117
this .onErrorOption = onErrorOption ;
118
+ this .terminationFuture = new CompletableFuture <>();
119
+
115
120
logger .logDebug ("Channel={} created for table={}" , this .channelName , this .tableName );
116
121
}
117
122
@@ -255,20 +260,31 @@ void invalidate() {
255
260
rowSequencer );
256
261
}
257
262
258
- /** @return a boolean to indicate whether the channel is closed or not */
263
+ /**
264
+ * @return {@code true} if the channel was closed, {@code false} otherwise. A return value of
265
+ * {@code true} does not mean that the data is committed. For this, you should call {@link
266
+ * #close()} and wait on the returned future.
267
+ */
259
268
@ Override
260
269
public boolean isClosed () {
261
- return this .isClosed ;
270
+ return this .isClosed . get () ;
262
271
}
263
272
264
- /** Mark the channel as closed */
265
- void markClosed () {
266
- this .isClosed = true ;
267
- logger .logDebug (
268
- "Channel is closed, name={}, channel sequencer={}, row sequencer={}" ,
269
- getFullyQualifiedName (),
270
- channelSequencer ,
271
- rowSequencer );
273
+ /**
274
+ * Mark the channel as closed atomically.
275
+ *
276
+ * @return {@code true} if the channel was already closed, {@code false} otherwise.
277
+ */
278
+ boolean markClosed () {
279
+ final boolean wasAlreadyClosed = this .isClosed .getAndSet (true );
280
+ if (!wasAlreadyClosed ) {
281
+ logger .logDebug (
282
+ "Channel is closed, name={}, channel sequencer={}, row sequencer={}" ,
283
+ getFullyQualifiedName (),
284
+ channelSequencer ,
285
+ rowSequencer );
286
+ }
287
+ return wasAlreadyClosed ;
272
288
}
273
289
274
290
/**
@@ -281,7 +297,9 @@ CompletableFuture<Void> flush(boolean closing) {
281
297
// Skip this check for closing because we need to set the channel to closed first and then flush
282
298
// in case there is any leftover rows
283
299
if (isClosed () && !closing ) {
284
- throw new SFException (ErrorCode .CLOSED_CHANNEL );
300
+ final CompletableFuture <Void > res = new CompletableFuture <>();
301
+ res .completeExceptionally (new SFException (ErrorCode .CLOSED_CHANNEL ));
302
+ return res ;
285
303
}
286
304
287
305
// Simply return if there is no data in the channel, this might not work if we support public
@@ -294,38 +312,51 @@ CompletableFuture<Void> flush(boolean closing) {
294
312
}
295
313
296
314
/**
297
- * Close the channel (this will flush in-flight buffered data)
315
+ * Close the channel and flush in-flight buffered data.
298
316
*
299
- * @return future which will be complete when the channel is closed
317
+ * @return a {@link CompletableFuture} which will be completed when the channel is closed and the
318
+ * data is successfully committed or has failed to be committed for some reason.
300
319
*/
301
320
@ Override
302
321
public CompletableFuture <Void > close () {
303
322
checkValidation ();
304
323
305
- if (isClosed ()) {
306
- return CompletableFuture .completedFuture (null );
307
- }
324
+ if (!markClosed ()) {
325
+ this .owningClient .removeChannelIfSequencersMatch (this );
308
326
309
- markClosed ();
310
- this .owningClient .removeChannelIfSequencersMatch (this );
311
- return flush (true )
312
- .thenRunAsync (
313
- () -> {
314
- List <SnowflakeStreamingIngestChannelInternal > uncommittedChannels =
315
- this .owningClient .verifyChannelsAreFullyCommitted (
316
- Collections .singletonList (this ));
317
-
318
- this .arrowBuffer .close ();
319
-
320
- // Throw an exception if the channel has any uncommitted rows
321
- if (!uncommittedChannels .isEmpty ()) {
322
- throw new SFException (
323
- ErrorCode .CHANNEL_WITH_UNCOMMITTED_ROWS ,
324
- uncommittedChannels .stream ()
325
- .map (SnowflakeStreamingIngestChannelInternal ::getFullyQualifiedName )
326
- .collect (Collectors .toList ()));
327
- }
328
- });
327
+ // here we flush even if removal fails because, for some reason,
328
+ // the client sequencers did not match.
329
+ // This does not seem like the desirable behavior.
330
+
331
+ flush (true )
332
+ .thenRunAsync (
333
+ () -> {
334
+ List <SnowflakeStreamingIngestChannelInternal > uncommittedChannels =
335
+ this .owningClient .verifyChannelsAreFullyCommitted (
336
+ Collections .singletonList (this ));
337
+
338
+ this .arrowBuffer .close ();
339
+
340
+ // Throw an exception if the channel has any uncommitted rows
341
+ if (!uncommittedChannels .isEmpty ()) {
342
+ throw new SFException (
343
+ ErrorCode .CHANNEL_WITH_UNCOMMITTED_ROWS ,
344
+ uncommittedChannels .stream ()
345
+ .map (SnowflakeStreamingIngestChannelInternal ::getFullyQualifiedName )
346
+ .collect (Collectors .toList ()));
347
+ }
348
+ })
349
+ .handle (
350
+ (ignored , t ) -> {
351
+ if (t != null ) {
352
+ terminationFuture .completeExceptionally (t );
353
+ } else {
354
+ terminationFuture .complete (null );
355
+ }
356
+ return null ;
357
+ });
358
+ }
359
+ return terminationFuture ;
329
360
}
330
361
331
362
/**
0 commit comments