Skip to content

Commit c98e048

Browse files
authored
Fix to run finalizers once when there's an exception during releasing (#336)
1 parent 16c9a21 commit c98e048

File tree

3 files changed

+51
-21
lines changed

3 files changed

+51
-21
lines changed

core/src/main/scala/ox/resource.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ def releaseCloseableAfterScope(toRelease: AutoCloseable)(using OxUnsupervised):
2929
* [[uninterruptible]]. To use multiple resources, consider creating a [[supervised]] scope and [[useInScope]] method.
3030
*/
3131
inline def use[R, T](inline acquire: R, inline release: R => Unit)(inline f: R => T): T =
32-
val r = acquire
33-
try f(r)
34-
finally uninterruptible(release(r))
32+
useInterruptible(acquire, r => uninterruptible(release(r)))(f)
3533

3634
/** Use the given resource, acquired using `acquire` and released using `release` in the given `f` code block. Releasing might be
3735
* interrupted. To use multiple resources, consider creating a [[supervised]] scope and [[useInScope]] method.

core/src/main/scala/ox/unsupervised.scala

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ private[ox] def scopedWithCapability[T](capability: Ox)(f: Ox ?=> T): T =
3030
es.tail.foreach(e.addSuppressed)
3131
throw e
3232

33-
val herd = capability.herd
34-
val finalizers = capability.finalizers
3533
def runFinalizers(result: Either[Throwable, T]): T =
36-
val fs = finalizers.get
34+
val fs = capability.finalizers.get
3735
if fs.isEmpty then result.fold(throw _, identity)
3836
else
3937
val es = uninterruptible {
@@ -51,17 +49,23 @@ private[ox] def scopedWithCapability[T](capability: Ox)(f: Ox ?=> T): T =
5149
end if
5250
end runFinalizers
5351

54-
val previousScope = currentScope.get()
55-
currentScope.set(capability)
56-
try
57-
val t =
58-
try f(using capability)
59-
finally herd.interruptAllAndJoinUntilCompleted()
52+
def runWithCurrentScopeSet =
53+
val result =
54+
try
55+
Right(
56+
try f(using capability)
57+
finally capability.herd.interruptAllAndJoinUntilCompleted()
58+
)
59+
catch case e: Throwable => Left(e)
6060

6161
// running the finalizers only once we are sure that all child threads have been terminated, so that no new
6262
// finalizers are added, and none are lost
63-
runFinalizers(Right(t))
64-
catch case e: Throwable => runFinalizers(Left(e))
63+
runFinalizers(result)
64+
end runWithCurrentScopeSet
65+
66+
val previousScope = currentScope.get()
67+
try
68+
currentScope.set(capability)
69+
runWithCurrentScopeSet
6570
finally currentScope.set(previousScope)
66-
end try
6771
end scopedWithCapability

core/src/test/scala/ox/ResourceTest.scala

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class ResourceTest extends AnyFlatSpec with Matchers:
5050
trail.get shouldBe Vector("allocate 1", "allocate 2", "release 2", "release 1", "exception")
5151
}
5252

53-
it should "release resources when there's an exception during releasing" in {
53+
it should "release resources when there's an exception during releasing (normal resutl)" in {
5454
val trail = Trail()
5555

5656
try
@@ -60,22 +60,50 @@ class ResourceTest extends AnyFlatSpec with Matchers:
6060
1
6161
} { n =>
6262
trail.add(s"release $n")
63-
throw new RuntimeException()
63+
throw new RuntimeException("e1")
6464
}
6565
val r2 = useInScope {
6666
trail.add("allocate 2");
6767
2
6868
} { n =>
6969
trail.add(s"release $n")
70-
throw new RuntimeException()
70+
throw new RuntimeException("e2")
7171
}
7272
r1 shouldBe 1
7373
r2 shouldBe 2
74-
throw new RuntimeException
74+
r1 + r2
7575
}
76-
catch case _ => trail.add("exception")
76+
catch case e => trail.add(s"exception ${e.getMessage}")
7777
end try
78-
trail.get shouldBe Vector("allocate 1", "allocate 2", "release 2", "release 1", "exception")
78+
trail.get shouldBe Vector("allocate 1", "allocate 2", "release 2", "release 1", "exception e2")
79+
}
80+
81+
it should "release resources when there's an exception during releasing (exceptional resutl)" in {
82+
val trail = Trail()
83+
84+
try
85+
unsupervised {
86+
val r1 = useInScope {
87+
trail.add("allocate 1");
88+
1
89+
} { n =>
90+
trail.add(s"release $n")
91+
throw new RuntimeException("e1")
92+
}
93+
val r2 = useInScope {
94+
trail.add("allocate 2");
95+
2
96+
} { n =>
97+
trail.add(s"release $n")
98+
throw new RuntimeException("e2")
99+
}
100+
r1 shouldBe 1
101+
r2 shouldBe 2
102+
throw new RuntimeException("e3")
103+
}
104+
catch case e => trail.add(s"exception ${e.getMessage}")
105+
end try
106+
trail.get shouldBe Vector("allocate 1", "allocate 2", "release 2", "release 1", "exception e3")
79107
}
80108

81109
it should "release registered resources" in {

0 commit comments

Comments
 (0)