@@ -35,7 +35,7 @@ import org.apache.pekko
35
35
import pekko .Done
36
36
import pekko .stream .{ AbruptTerminationException , ActorAttributes , ActorMaterializer , SystemMaterializer }
37
37
import pekko .stream .ActorAttributes .supervisionStrategy
38
- import pekko .stream .Supervision .{ restartingDecider , resumingDecider }
38
+ import pekko .stream .Supervision .{ restartingDecider , resumingDecider , stoppingDecider }
39
39
import pekko .stream .impl .{ PhasedFusingActorMaterializer , StreamSupervisor }
40
40
import pekko .stream .impl .StreamSupervisor .Children
41
41
import pekko .stream .testkit .{ StreamSpec , TestSubscriber }
@@ -410,6 +410,198 @@ class FlowMapWithResourceSpec extends StreamSpec(UnboundedMailboxConfig) {
410
410
Await .result(promise.future, 3 .seconds) shouldBe Done
411
411
}
412
412
413
+ " will close the autocloseable resource when upstream complete" in {
414
+ val closedCounter = new AtomicInteger (0 )
415
+ val create = () =>
416
+ new AutoCloseable {
417
+ override def close (): Unit = closedCounter.incrementAndGet()
418
+ }
419
+ val (pub, sub) = TestSource
420
+ .probe[Int ]
421
+ .mapWithResource(create, (_ : AutoCloseable , count) => count)
422
+ .toMat(TestSink .probe)(Keep .both)
423
+ .run()
424
+ sub.expectSubscription().request(2 )
425
+ closedCounter.get shouldBe 0
426
+ pub.sendNext(1 )
427
+ sub.expectNext(1 )
428
+ closedCounter.get shouldBe 0
429
+ pub.sendComplete()
430
+ sub.expectComplete()
431
+ closedCounter.get shouldBe 1
432
+ }
433
+
434
+ " will close the autocloseable resource when upstream fail" in {
435
+ val closedCounter = new AtomicInteger (0 )
436
+ val create = () =>
437
+ new AutoCloseable {
438
+ override def close (): Unit = closedCounter.incrementAndGet()
439
+ }
440
+ val (pub, sub) = TestSource
441
+ .probe[Int ]
442
+ .mapWithResource(create, (_ : AutoCloseable , count) => count)
443
+ .toMat(TestSink .probe)(Keep .both)
444
+ .run()
445
+ sub.expectSubscription().request(2 )
446
+ closedCounter.get shouldBe 0
447
+ pub.sendNext(1 )
448
+ sub.expectNext(1 )
449
+ closedCounter.get shouldBe 0
450
+ pub.sendError(ex)
451
+ sub.expectError(ex)
452
+ closedCounter.get shouldBe 1
453
+ }
454
+
455
+ " will close the autocloseable resource when downstream cancel" in {
456
+ val closedCounter = new AtomicInteger (0 )
457
+ val create = () =>
458
+ new AutoCloseable {
459
+ override def close (): Unit = closedCounter.incrementAndGet()
460
+ }
461
+ val (pub, sub) = TestSource
462
+ .probe[Int ]
463
+ .mapWithResource(create, (_ : AutoCloseable , count) => count)
464
+ .toMat(TestSink .probe)(Keep .both)
465
+ .run()
466
+ val subscription = sub.expectSubscription()
467
+ subscription.request(2 )
468
+ closedCounter.get shouldBe 0
469
+ pub.sendNext(1 )
470
+ sub.expectNext(1 )
471
+ closedCounter.get shouldBe 0
472
+ subscription.cancel()
473
+ pub.expectCancellation()
474
+ closedCounter.get shouldBe 1
475
+ }
476
+
477
+ " will close the autocloseable resource when downstream fail" in {
478
+ val closedCounter = new AtomicInteger (0 )
479
+ val create = () =>
480
+ new AutoCloseable {
481
+ override def close (): Unit = closedCounter.incrementAndGet()
482
+ }
483
+ val (pub, sub) = TestSource
484
+ .probe[Int ]
485
+ .mapWithResource(create, (_ : AutoCloseable , count) => count)
486
+ .toMat(TestSink .probe)(Keep .both)
487
+ .run()
488
+ sub.request(2 )
489
+ closedCounter.get shouldBe 0
490
+ pub.sendNext(1 )
491
+ sub.expectNext(1 )
492
+ closedCounter.get shouldBe 0
493
+ sub.cancel(ex)
494
+ pub.expectCancellationWithCause(ex)
495
+ closedCounter.get shouldBe 1
496
+ }
497
+
498
+ " will close the autocloseable resource on abrupt materializer termination" in {
499
+ val closedCounter = new AtomicInteger (0 )
500
+ @ nowarn(" msg=deprecated" )
501
+ val mat = ActorMaterializer ()
502
+ val promise = Promise [Done ]()
503
+ val create = () =>
504
+ new AutoCloseable {
505
+ override def close (): Unit = {
506
+ closedCounter.incrementAndGet()
507
+ promise.complete(Success (Done ))
508
+ }
509
+ }
510
+ val matVal = Source
511
+ .single(1 )
512
+ .mapWithResource(create, (_ : AutoCloseable , count) => count)
513
+ .runWith(Sink .never)(mat)
514
+ closedCounter.get shouldBe 0
515
+ mat.shutdown()
516
+ matVal.failed.futureValue shouldBe an[AbruptTerminationException ]
517
+ Await .result(promise.future, 3 .seconds) shouldBe Done
518
+ closedCounter.get shouldBe 1
519
+ }
520
+
521
+ " continue with autoCloseable when Strategy is Resume and exception happened" in {
522
+ val closedCounter = new AtomicInteger (0 )
523
+ val create = () =>
524
+ new AutoCloseable {
525
+ override def close (): Unit = closedCounter.incrementAndGet()
526
+ }
527
+ val p = Source
528
+ .fromIterator(() => (0 to 50 ).iterator)
529
+ .mapWithResource(create,
530
+ (_ : AutoCloseable , elem) => {
531
+ if (elem == 10 ) throw TE (" " ) else elem
532
+ })
533
+ .withAttributes(supervisionStrategy(resumingDecider))
534
+ .runWith(Sink .asPublisher(false ))
535
+ val c = TestSubscriber .manualProbe[Int ]()
536
+
537
+ p.subscribe(c)
538
+ val sub = c.expectSubscription()
539
+
540
+ (0 to 48 ).foreach(i => {
541
+ sub.request(1 )
542
+ c.expectNext() should === (if (i < 10 ) i else i + 1 )
543
+ })
544
+ sub.request(1 )
545
+ c.expectNext(50 )
546
+ c.expectComplete()
547
+ closedCounter.get shouldBe 1
548
+ }
549
+
550
+ " close and open stream with autocloseable again when Strategy is Restart" in {
551
+ val closedCounter = new AtomicInteger (0 )
552
+ val create = () =>
553
+ new AutoCloseable {
554
+ override def close (): Unit = closedCounter.incrementAndGet()
555
+ }
556
+ val p = Source
557
+ .fromIterator(() => (0 to 50 ).iterator)
558
+ .mapWithResource(create,
559
+ (_ : AutoCloseable , elem) => {
560
+ if (elem == 10 || elem == 20 ) throw TE (" " ) else elem
561
+ })
562
+ .withAttributes(supervisionStrategy(restartingDecider))
563
+ .runWith(Sink .asPublisher(false ))
564
+ val c = TestSubscriber .manualProbe[Int ]()
565
+
566
+ p.subscribe(c)
567
+ val sub = c.expectSubscription()
568
+
569
+ (0 to 30 ).filter(i => i != 10 && i != 20 ).foreach(i => {
570
+ sub.request(1 )
571
+ c.expectNext() shouldBe i
572
+ closedCounter.get should === (if (i < 10 ) 0 else if (i < 20 ) 1 else 2 )
573
+ })
574
+ sub.cancel()
575
+ }
576
+
577
+ " stop stream with autoCloseable when Strategy is Stop and exception happened" in {
578
+ val closedCounter = new AtomicInteger (0 )
579
+ val create = () =>
580
+ new AutoCloseable {
581
+ override def close (): Unit = closedCounter.incrementAndGet()
582
+ }
583
+ val p = Source
584
+ .fromIterator(() => (0 to 50 ).iterator)
585
+ .mapWithResource(create,
586
+ (_ : AutoCloseable , elem) => {
587
+ if (elem == 10 ) throw TE (" " ) else elem
588
+ })
589
+ .withAttributes(supervisionStrategy(stoppingDecider))
590
+ .runWith(Sink .asPublisher(false ))
591
+ val c = TestSubscriber .manualProbe[Int ]()
592
+
593
+ p.subscribe(c)
594
+ val sub = c.expectSubscription()
595
+
596
+ (0 to 9 ).foreach(i => {
597
+ sub.request(1 )
598
+ c.expectNext() shouldBe i
599
+ })
600
+ sub.request(1 )
601
+ c.expectError()
602
+ closedCounter.get shouldBe 1
603
+ }
604
+
413
605
}
414
606
override def afterTermination (): Unit = {
415
607
fs.close()
0 commit comments