Skip to content

Commit ff52a8b

Browse files
authored
Fix destroyOnExit default forwarding, make destroy recursive by default (#359)
* Previously a bunch of `bincompat` forwarders were incorrectly setting `destroyOnExit = false` when the default is `true`, this makes them consistently pass `destroyOnExit = false` when an explicit value is not provided * `os.proc.call` was forgetting to forward `shutdownGracePeriod` and `destroyOnExit` to the underlying `os.proc.spawn` * `SubProcess#destroy` was not destroying descendent processes. While this has always been the default, it has always been confusing, and often resulted in leaking orphan processes. This PR makes it recursively destroy child processes by default, unless passed `recursive = false`. `destroyOnExit` and on timeout now is always recursive Dropping Scala-Native support for now due to: ``` os.native[2.12.17].test.nativeLink scala.scalanative.linker.LinkingException: Unreachable symbols found after classloading run. It can happen when using dependencies not cross-compiled for Scala Native or not yet ported JDK definitions. ```` Will open an issue to figure it out asynchronously
1 parent 3aea8be commit ff52a8b

File tree

6 files changed

+41
-23
lines changed

6 files changed

+41
-23
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- uses: actions/setup-java@v4
4747
with:
4848
distribution: 'temurin'
49-
java-version: 8
49+
java-version: 11
5050

5151
- run: ./mill -i -k __.mimaReportBinaryIssues
5252

build.mill

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,14 @@ object os extends Module {
194194
object nohometest extends ScalaTests with OsLibTestModule
195195
}
196196

197-
object native extends Cross[OsNativeModule](scalaVersions)
197+
/*object native extends Cross[OsNativeModule](scalaVersions)
198198
trait OsNativeModule extends OsModule with ScalaNativeModule {
199199
def scalaNativeVersion = "0.5.2"
200200
object test extends ScalaNativeTests with OsLibTestModule {
201201
def nativeLinkStubs = true
202202
}
203203
object nohometest extends ScalaNativeTests with OsLibTestModule
204-
}
204+
}*/
205205

206206
object watch extends Module {
207207
object jvm extends Cross[WatchJvmModule](scalaVersions)

os/src/ProcessOps.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ object call {
7171
check = check,
7272
propagateEnv = propagateEnv,
7373
shutdownGracePeriod = timeoutGracePeriod,
74-
destroyOnExit = false
74+
destroyOnExit = true
7575
)
7676
}
7777
}
@@ -130,7 +130,7 @@ object spawn {
130130
mergeErrIntoOut = mergeErrIntoOut,
131131
propagateEnv = propagateEnv,
132132
shutdownGracePeriod = 100,
133-
destroyOnExit = false
133+
destroyOnExit = true
134134
)
135135
}
136136
}
@@ -219,7 +219,9 @@ case class proc(command: Shellable*) {
219219
chunks.add(Right(new geny.Bytes(java.util.Arrays.copyOf(buf, n))))
220220
),
221221
mergeErrIntoOut,
222-
propagateEnv
222+
propagateEnv,
223+
shutdownGracePeriod = shutdownGracePeriod,
224+
destroyOnExit = destroyOnExit
223225
)
224226

225227
sub.join(timeout, shutdownGracePeriod)
@@ -277,7 +279,7 @@ case class proc(command: Shellable*) {
277279
check,
278280
propagateEnv,
279281
timeoutGracePeriod,
280-
destroyOnExit = false
282+
destroyOnExit = true
281283
)
282284

283285
/**
@@ -374,7 +376,7 @@ case class proc(command: Shellable*) {
374376
mergeErrIntoOut = mergeErrIntoOut,
375377
propagateEnv = propagateEnv,
376378
shutdownGracePeriod = 100,
377-
destroyOnExit = false
379+
destroyOnExit = true
378380
)
379381

380382
/**

os/src/SubProcess.scala

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ class SubProcess(
145145
*/
146146
def destroy(): Unit = destroy(shutdownGracePeriod = this.shutdownGracePeriod, async = false)
147147

148+
def destroy(
149+
shutdownGracePeriod: Long,
150+
async: Boolean
151+
): Unit = destroy(shutdownGracePeriod, async, recursive = true)
152+
148153
/**
149154
* Destroys the subprocess, via the underlying JVM APIs, with configurable levels of
150155
* aggressiveness:
@@ -155,26 +160,37 @@ class SubProcess(
155160
* that was used to spawned the process, but can be set to 0
156161
* (i.e. force exit immediately) or -1 (i.e. never force exit)
157162
* or anything in between. Typically defaults to 100 milliseconds.
163+
* @param recursive whether or not to also destroy this process's own child processes and
164+
* descendents. Each parent process is destroyed before its children, to
165+
* ensure that when we are destroying the child processes no other children
166+
* can be spawned concurrently
158167
*/
159168
def destroy(
160169
shutdownGracePeriod: Long = this.shutdownGracePeriod,
161-
async: Boolean = false
170+
async: Boolean = false,
171+
recursive: Boolean = true
162172
): Unit = {
163-
wrapped.destroy()
164-
if (!async) {
165-
val now = System.currentTimeMillis()
166-
167-
while (
168-
wrapped.isAlive && (shutdownGracePeriod == -1 || System.currentTimeMillis() - now < shutdownGracePeriod)
169-
) {
170-
Thread.sleep(1)
171-
}
172173

173-
if (wrapped.isAlive) {
174-
println("wrapped.destroyForcibly()")
175-
wrapped.destroyForcibly()
174+
def destroy0(p: ProcessHandle) = {
175+
p.destroy()
176+
if (!async) {
177+
val now = System.currentTimeMillis()
178+
179+
while (
180+
p.isAlive && (shutdownGracePeriod == -1 || System.currentTimeMillis() - now < shutdownGracePeriod)
181+
) {
182+
Thread.sleep(1)
183+
}
184+
185+
if (p.isAlive) p.destroyForcibly()
176186
}
177187
}
188+
def rec(p: ProcessHandle): Unit = {
189+
destroy0(p)
190+
p.children().forEach(c => rec(c))
191+
}
192+
if (recursive) rec(wrapped.toHandle)
193+
else destroy0(wrapped.toHandle)
178194
}
179195

180196
@deprecated("Use destroy(shutdownGracePeriod = 0)")

os/test/src-jvm/SpawningSubprocessesNewTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ object SpawningSubprocessesNewTests extends TestSuite {
220220
}
221221
}
222222

223-
test("destroyNoGrace") {
223+
test("destroyNoGrace") - retry(3) {
224224
if (Unix()) {
225225
val temp = os.temp()
226226
val subprocess = os.spawn((sys.env("TEST_SPAWN_EXIT_HOOK_ASSEMBLY"), temp))

os/test/src/ZipOpTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ object ZipOpTests extends TestSuite {
173173
wd / "unzipAllExceptExcludingCertainFiles/one.txt"
174174
)
175175

176-
assert(paths == expected)
176+
assert(paths.toSet == expected.toSet)
177177
}
178178

179179
test("zipList") - prep { wd =>

0 commit comments

Comments
 (0)