@@ -35,7 +35,7 @@ import org.apache.pekko
3535import pekko .Done
3636import pekko .stream .{ AbruptTerminationException , ActorAttributes , ActorMaterializer , SystemMaterializer }
3737import pekko .stream .ActorAttributes .supervisionStrategy
38- import pekko .stream .Supervision .{ restartingDecider , resumingDecider }
38+ import pekko .stream .Supervision .{ restartingDecider , resumingDecider , stoppingDecider }
3939import pekko .stream .impl .{ PhasedFusingActorMaterializer , StreamSupervisor }
4040import pekko .stream .impl .StreamSupervisor .Children
4141import pekko .stream .testkit .{ StreamSpec , TestSubscriber }
@@ -410,6 +410,198 @@ class FlowMapWithResourceSpec extends StreamSpec(UnboundedMailboxConfig) {
410410 Await .result(promise.future, 3 .seconds) shouldBe Done
411411 }
412412
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+
413605 }
414606 override def afterTermination (): Unit = {
415607 fs.close()
0 commit comments