Skip to content

feat: keep terminal sessions running in the background - #16

Merged
oonid merged 48 commits into
mainfrom
feat/background-sessions
Oct 18, 2026
Merged

oonid merged 48 commits into
mainfrom
feat/background-sessions

Conversation

@oonid

@oonid oonid commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Implements docs/design/background-sessions.md, following docs/plans/background-sessions.md.

A container's terminal session now keeps running after its screen closes, so a long-running program such as the Hermes gateway can run around the clock.

What changes

  • ManagedSession (session/ManagedSession.kt): one terminal session (PTY, termlib emulator, reader thread) that keeps draining output with no screen attached and is shown by at most one screen. End is gentle first: SIGTERM and SIGCONT go to every process in the session except the login shell (proot tracees by TracerPid, so daemons that left the shell's tree are included), and End waits up to 3 s for them to exit. Then it sends EOF, after 2 s kills the login shell (proot's --kill-on-exit takes the rest), and after 2 s more kills proot. The worst case is about 9 s.
  • SessionManager (session/SessionManager.kt): app-wide, one session per container, owned by App. It publishes the running list, starts the foreground service at 0 → 1, and holds the Keep CPU awake flag (off each time sessions start from zero) and the background error.
  • ProotPty / ProotSessionFactory: start sessions through ProotLauncher. The PTY starts at the emulator's 24x80. The emulator runs on the main looper, because termlib resizes on the caller's thread.
  • SessionService: a thin specialUse foreground service. It keeps the process alive while any session runs, owns the notification (End all, Keep CPU awake / Let CPU sleep) and the partial wake lock, and stops itself on its main thread when the running list empties.
  • TerminalActivity: a view onto a session. Back asks End session / Keep running / Cancel. It shows "Session ended (exit N)" and "Couldn't start : ". A program that ends while the screen is not visible (Back with Keep running, Home, switching apps, swipe) posts a "session ended" notice. A configuration-change recreation (dark mode, theme) keeps the same session, including an ended one. Opening a container whose session is still ending shows "Ending the previous session…" and starts fresh once it has ended, or gives up after 10 s. The notification permission is requested once, and the running notification is posted again once it is granted.
  • Container list: "● Running" on containers with a session. While a session runs, a Keep CPU awake switch is pinned above the list, so it stays reachable when notifications are denied. While Keep CPU awake is on and the app is not exempt from battery optimization, a banner with Allow shows, and a background-error banner shows when the service fails. Remove ends a running session first: it waits up to 10 s and cancels the removal with a message if the session is still running.
  • Docs: the design, the plan, and "Session lifetime and Back" in docs/android-apk-architecture.md.

Changes beyond the plan's code

These came out of review and device testing. Each is its own commit.

  • ManagedSession guards PTY access after close, contains reader failures, and makes end() idempotent.
  • SessionManager publishes running in the right order around the foreground start. detach recomputes running when it drops an ended session (a race could leave a session marked running forever).
  • The service owns its stop, using stopSelfResult(lastStartId). Before, stopService from the reader thread could crash the app ("did not then call startForeground") when a login failed at once, and could leave a stale notification behind.
  • The emulator runs on the main looper. Off the main looper, termlib's resize on the main thread raced processPendingUpdates and could throw on the emulator thread, killing the process.
  • Remove waits 10 s, not 5 s: the worst-case end takes longer than 5 s (about 9 s with the gentle step added later). If the session is still running, Remove cancels instead of deleting files under a live proot.
  • The running notification is posted again after the permission is granted, from the prompt or from Settings.
  • Tapping the notification returns to the one list (CLEAR_TOP | SINGLE_TOP).
  • Keep CPU awake toggles are ignored when nothing runs, and the switch is hidden when the service failed to start.
  • The session banners are pinned above the list. Inside the LazyColumn they appeared above the viewport.
  • The battery banner no longer claims that Doze pauses sessions (see the Doze results below).

From the pre-merge review (two rounds, each re-reviewed):

  • The "session ended" notice depends on whether the screen is visible, not just attached. Before, leaving with Home suppressed it. An ended session also survives a configuration-change recreation instead of being silently replaced by a fresh login.
  • End sends SIGTERM and SIGCONT to every proot tracee first, so the Hermes gateway (which drains on SIGTERM) and daemons started with setsid shut down cleanly. SIGHUP is not sent: Python's default for it is an immediate exit, and daemons often treat it as "reload".
  • Opening during End waits for the old session to end instead of attaching to a dying one.
  • End's EOF is written from its own thread, so a full PTY input queue cannot stall the kill steps. When the reader fails and the exit code is unknown, the shell is killed before proot.
  • The background error clears once the service starts. Keep CPU awake is read-only outside SessionManager. The notification drops its battery note once the exemption is granted. The battery-settings fallback can't crash.
  • Docs: Remove of a running session, and the 24/7 limits (the Android 12+ phantom-process limit, and Android 13+'s Task Manager "Stop").

From the final independent review:

  • When End has to kill proot, it first SIGKILLs every tracee. proot has no PTRACE_O_EXITKILL, so a stuck tracee could otherwise outlive it, hold the PTY and leave the session marked running for good.
  • The "Ending…" screen starts the fresh session only while it is started: leaving with Home no longer starts a login nobody sees.
  • Keep running marks the screen hidden before it closes, so an exit during the close transition still posts the notice.
  • Three timing-sensitive tests got wider margins; a change-detector test was removed. The plan is marked historical.

Verification

  • Unit tests: 62 passed (ManagedSession 22, SessionManager 29, ProcStat 11), also in five repeated runs.
  • Suites: proot-engine instrumented 8/8; host cargo test 169 passed; pr-cli test alpine and pr-cli test ubuntu 55/55 each; in-app pit all 55/55 (run while the session was in the background).

Device checks (Android 16, build before the pre-merge fixes; the End timings here predate the SIGTERM step, which is re-measured below):

  • Open, type, output: works. stty size matches the visible grid.
  • First start: the notification permission is asked once. After Allow, "1 session running / alpine" shows at once. Granting later in Settings shows it on returning to the list.
  • The service runs as specialUse (types=0x40000000).
  • Keep running: "● Running" in the list, and proot stays alive. Reopening shows output produced while the screen was closed. Ctrl-C works.
  • End session on a busy sleep 1000: all processes gone in about 2.4 s. This kernel has no /proc/<pid>/task/<pid>/children, so the fallback /proc scan finds the login shell.
  • End session from the prompt 20 times in a row: no stale "● Running", no service left.
  • exit: "Session ended (exit 0)"; one Back returns to the list without a prompt.
  • Login that fails at once (alpine /bin/sh temporarily missing): "Session ended (exit 1)", no crash, the service stops.
  • End, then reopen 0.3 / 0.6 / 1.0 s later: the new session is in the foreground with one notification every time.
  • Chatty output (while :; do date; done), 30 keyboard toggles and 10 rotations: no crash.
  • Tapping the notification with a terminal on top: one list, the terminal closed, the session still running.
  • Last session ends with Keep CPU awake on, by exit and by End all with two sessions: no notification, wake lock or service left.
  • Keep CPU awake switch: holds the pr:sessions partial wake lock, and resets when sessions end.
  • Lifecycle 1 and 3: a counter kept running after Keep running and after swiping the app from recents (58 lines in 60 s, same process).
  • Lifecycle 2: reattach shows the screen; Ctrl-C stops the loop.
  • Lifecycle 5: sleep 5; exit 3 with Keep running posts "ubuntu: session ended (exit 3)".
  • Lifecycle 6: End all with two sessions ends both and removes the ongoing notification.
  • Lifecycle 7: with the notification permission revoked, the service is still in the foreground, the in-app switch works, and the counter advanced 59 lines in 60 s.
  • Remove while running (a throwaway busybox container, with a shell ignoring HUP and sleep 1000): the session ended before removal, then the rootfs was wiped.

After the pre-merge fixes:

  • Home, then exit 3: the notice "alpine: session ended (exit 3)" is posted, and returning shows "Session ended (exit 3)".
  • Dark-mode recreation with an ended session keeps "Session ended (exit N)" (no fresh login, no notice). With a running session, the same session (same proot) comes back with its screen.
  • Switching apps away and back, or exit with the terminal in front: no notice.
  • End with a setsid daemon outside the shell's tree that traps SIGTERM: the daemon got SIGTERM, and the session ended in 0.4 s.
  • End, then reopen while a TERM-ignoring program keeps the session alive: "Ending the previous session…", then a fresh login once the old session is gone.
  • The battery banner and notification show the new text; Allow opens the system dialog; three resumes cause no extra service starts.
  • Keep running, then the program exits: the notice is posted. Reopen during End, then Home: no session starts while away; returning starts the fresh one.
  • Smoke test of the final build: open, Keep running, "● Running", the switch, the specialUse notification, reattach with the earlier output, and End all from the notification, which leaves no process, service, notification or wake lock.

Doze (lifecycle 4):

  • Keep CPU awake on, screen off for 120 s: the counter advanced.
  • dumpsys battery unplug plus deviceidle force-idle: the counter advanced both with and without the battery-optimization exemption. A network probe (wget every 5 s) succeeded 15/15 without the exemption and 9/9 with it.
  • Android lets a foreground-service process keep its wake lock and network in Doze, so the design's premise that Doze pauses sessions does not hold. The exemption banner stays as a guard against vendor battery savers, with corrected text.

Gateway check (the design's target check):

  • Setup: hermes gateway run in the hermes container with its OpenAI-compatible API server on; Keep running, Keep CPU awake on, battery exemption granted.
  • The gateway reached the model provider over Wi-Fi (a temporary forwarder on the host, accepting only the phone). Requests came in from the host through adb forward.
  • Screen off, dumpsys battery unplug, deviceidle force-idle from 06:40:53 UTC.
  • 7 requests to /v1/chat/completions under forced idle, one about every 5 minutes (06:45 to 07:13 UTC). All 7 returned HTTP 200 with OK, in 2 to 4 s each. Deep idle was still in force at the end.
  • End session on the gateway: it logged Shutdown context: signal=SIGTERM, disconnected its adapters, closed its database, and stopped in 0.31 s. Its lifecycle ledger records graceful_shutdown. The whole session ended in 0.7 s.
  • Afterwards the key, the forwarder, the adb forward and the config change were removed.

Known limits and follow-ups

  • End gives the programs in a session up to 3 s to exit after SIGTERM. A slower shutdown is cut off by the kill steps.
  • If any process's status can't be read, End falls back to signalling only the login shell's descendants, which misses daemons that left the tree.
  • The Android 12+ phantom-process limit and Android 13+'s Task Manager "Stop" can still end sessions (documented).
  • SessionService's start/stop handling and ProotPty's /proc scanning are device-verified only, with no automated tests yet.
  • The "1 session running" notification can briefly read "0 sessions running" when a login fails at once, before the service stops (cosmetic).
  • Rotating the container list during Remove's wait cancels the removal; the session has already ended. This predates the PR: the list's coroutine scope doesn't survive recreation.

oonid added 30 commits October 17, 2026 23:53
Seven tasks for docs/design/background-sessions.md: ManagedSession and
SessionManager test-first on the JVM, the ProotLauncher-backed PTY and
factory (emulator on its own HandlerThread), the specialUse foreground
service and notifications, TerminalActivity as a view onto a session,
the container list's running mark and banners, and the docs, suites,
device lifecycle checks and the gateway check.

Ending a session goes further than the design's "End closes the
session": EOF, then a kill of the login shell (proot's --kill-on-exit
ends the rest; proot does not set PTRACE_O_EXITKILL, so killing proot
first would leave its tracees running), then proot as a last resort,
and the PTY is closed only by the reader thread after its last read.
A ManagedSession owns a PTY and a reader thread that keeps draining output with no screen attached, records the exit code, tracks the one screen that shows it, and ends with EOF, then a kill of the program, then a forced kill, closing the PTY only after the reader's last read. JVM unit tests through a fake PTY.
…on reader failure

write, resize and the EOF in end() now take a lock shared with the reader's close and do nothing once the PTY is closed, so they can no longer reach a closed or reused file descriptor. The reader's teardown (exit code wait, close, release, exit state, onEnded) runs in a finally, so a throwing output or release callback ends the session instead of leaving it looking alive. A second end() is a no-op. Tests cover each case.
SessionManager keeps one ManagedSession per container alias, publishes the running aliases, starts the foreground service at 0 to 1 running sessions and stops it at 1 to 0 (resetting keep-awake), reports a foreground start failure instead of failing the session, posts a session-ended notice only for a program that exited with no screen attached and no End request, and keeps an ended session while a screen still shows it. Changes are serialised, so concurrent opens create one session.
SessionManager now starts or stops the foreground service, and resets keep-awake and the stale background error, before it publishes the new running list, so observers never see an empty list while the service is still up. A failing foreground stop is recorded as the background error instead of aborting the session-ended handling.
ProotPty adapts ProotLauncher.Session. Its kill signals the login shell, proot's only direct child, so proot's --kill-on-exit ends every process of the session; killing proot itself would leave its tracees running, so that is only the last resort. ProotSessionFactory runs each session's termlib emulator on its own HandlerThread, since termlib builds a snapshot on its looper after every burst of output.
If the emulator or ManagedSession could not be built after proot had started, the process, its master fd and the emulator thread were orphaned; create now tears them down and rethrows. Signalling is skipped for a non-positive pid, since a -1 would reach every process the app may signal.
SessionService keeps the process alive while sessions run, shows the running-sessions notification with End all and Keep CPU awake, holds a partial wake lock while the toggle is on, and stops at zero sessions. A session that exits with no screen attached posts a session-ended notification when notifications are allowed. App owns the SessionManager. specialUse is used because Android 15 limits dataSync to 6 hours a day.
…ke toggles

At the first session the service reads the running list when it starts, so the list is published before the start request. SessionService decides whether to stop from a fresh read after handling its action, and a Keep CPU awake tap delivered with no session running no longer turns the wake lock on for the next one.
TerminalActivity gets its session from SessionManager, attaches to it (closing any other screen showing it) and detaches when it closes; closing the screen no longer ends the session. Back keeps its order (keyboard, then selection) and then asks End session / Keep running / Cancel; once the session has ended it shows the exit code and Back returns without asking. A session that cannot start shows the reason. The notification permission is requested once, the first time a session starts.
Containers with a running session show Running. When Keep CPU awake is on and the app is not exempt from battery optimization, a banner explains that Doze will pause sessions and opens the system's exemption dialog; a foreground start failure shows its reason. Removing a container ends its session first.
Ending a busy session can take about 6 s (EOF, then the shell kill and the proot kill 2 s apart, then up to 2 s for the exit code), longer than the 5 s the remove flow waited before removing anyway. Wait 10 s, and if the session is still running, cancel the removal and say so instead of deleting files under a live proot.
Session lifetime and Back now describes SessionManager, the foreground service, the Back prompt, how End stops a session, and why the 24/7 case needs the battery-optimization exemption.
termlib resizes on the caller's thread (main) but applies output on
the emulator's looper without locking: updateLine reads rows unlocked
and indexes currentLines, which resize replaces. With the emulator on
its own HandlerThread a resize during output could throw
IndexOutOfBounds on that thread and kill the process.

Create the emulator on the main looper and drop the HandlerThread and
its quit on release. The docs now say why the emulator stays on main.
ManagedSession.finish() records the exit before it calls onEnded. A
detach in that gap removed the ended, screenless session from the map
without updating running, and onEnded then returned early because the
session was no longer in the map. The alias stayed in running, so the
service never stopped and the list kept showing it as running.

detach now calls updateRunning() when it removes a session. Tests
cover that window and a detach after exit without a reopen.
SessionManager stopped the service with stopService from whichever
thread ended the last session. If that happened before onStartCommand
ran, the system tore down a service whose startForeground was still
pending and crashed the app; a login that exits at once did this.
With Keep CPU awake on, the collector could also re-post the running
notification after the system had removed it.

ForegroundController now only starts the service. SessionService
tracks the last start id and whether it is in the foreground. When
running empties after startForeground, it removes its notification
and calls stopSelfResult with the last start id; onStartCommand calls
startForeground first and then stops the same way if nothing runs.
Tests assert the running list instead of stop calls.
With notifications denied the running notification is hidden, so
Keep CPU awake, and with it the battery optimization banner, could not
be reached.

While a session runs, the container list shows a "Keep CPU awake"
card with a switch bound to SessionManager.keepCpuAwake, above the
other banners. The notification action stays. Docs say the toggle is
in both places.
The first session starts the service before the user answers the
notification permission prompt, so its foreground notification is
posted while notifications are denied. The service only re-posts it
when the running list or Keep CPU awake changes, so after Allow no
notification appeared.

When the permission is granted and a session runs, TerminalActivity
starts the service again; onStartCommand calls startForeground, which
posts the notification. With no session running it stops itself.
The content intent used only FLAG_ACTIVITY_SINGLE_TOP, so tapping a
notification while a terminal was on top started a second container
list above it.

Add FLAG_ACTIVITY_CLEAR_TOP: the tap clears the terminal (it detaches,
and its session keeps running) and delivers the intent to the existing
MainActivity, which does not read its intent.
- Start the PTY at 24x80, the emulator's initial size. It defaulted
  to 50x200, and termlib skips the resize when the screen computes
  exactly 24x80, which left the PTY and the emulator out of step.
- The leave prompt's End session ends the session the screen shows
  instead of looking it up by alias again, which could end a newer
  session for the same container.
- Allow in the battery banner falls back to the battery optimization
  settings list when the request dialog has no handler.
- Import Composable in TerminalActivity instead of fully qualified
  annotations, and drop stray blank lines in App.
The design doc described an earlier SessionManager API: open returning
a ManagedSession, a sessions flow of SessionInfo and a separate
emulator factory seam. Describe the real one: open returns Opened or
Failed, running is a StateFlow of aliases, attach and detach connect
screens, and the session factory creates both the PTY and the
emulator.

The architecture table now says that a terminal restored from recents
after Android kills the process starts a fresh session.
The manager now owns the toggle and sets it only while a session is running,
so a tap landing as the last session ends cannot leave the next session awake.
Without the service there is no wake lock to hold, so the switch did nothing.
On resume of the container list, restart the service when notification
permission has just become granted while sessions run, so it posts again.
Nothing passes it any more; drops the parameter, its call in finish() and
the test that exercised it.
Inside the LazyColumn the banners item is empty while nothing runs, so when a session starts the list keeps its scroll anchor on the next card and the Keep CPU awake switch and banners appear above the viewport, out of sight. Show them in a Column above the list instead.
… savers

Measured under forced deep idle, a session kept its wake lock and network without the battery-optimization exemption: Android lets a foreground-service process keep both in Doze. Vendor battery savers can still stop an app that is not exempt, so the Allow banner stays, with text that no longer claims Doze pauses sessions. The design and architecture docs record the measurement.
TerminalActivity detached only in onDestroy, so after Home the screen
stayed attached and a program exiting in the background posted no
"session ended" notice. A recreation by a configuration change not in
configChanges (dark mode, theme) dropped an ended session and started
a fresh login in its place.

The attached screen now carries a visible state set in onStart and
onStop; the notice is posted when the user did not end the session and
no screen is visible. onDestroy during a configuration change detaches
with retainEnded, and the next open returns that session once.
End used to send EOF and then SIGKILL the login shell, so a background
program such as the gateway never had a chance to clean up.

Pty gains hangUp(): ProotPty walks /proc (the children file when
present, otherwise a stat scan) and sends SIGHUP to every descendant
of the login shell, not the shell or proot. The end thread now hangs
up first, waits up to the grace period if anything was signalled, and
only then sends EOF, kills the login shell and kills proot, stopping
as soon as the session ends.
Opening a container while its session was ending returned that
session, so the new screen attached and showed "Session ended" a
moment later.

open() now returns Ending(session) for a session whose end was
requested and that has not exited yet; it still never blocks.
TerminalActivity shows "Ending the previous session…", waits for that
session's exit, then opens again and starts a fresh session.
oonid added 18 commits October 18, 2026 13:05
- ManagedSession.finish() kills proot when the exit-code wait runs out,
  so a reader failure cannot leave proot running untracked.
- SessionService reports a successful startForeground through
  SessionManager.foregroundStarted(), which clears a stale
  backgroundError.
- keepCpuAwake is a read-only StateFlow; setKeepCpuAwake is the only
  way to change it.
- Returning to the list after the battery exemption is granted
  restarts the service while sessions run, so the notification drops
  the battery note.
- Allow catches a missing fallback battery settings screen too.
The spec now covers Remove while a session runs (end first, wait up to
10 s, cancel if it still runs). The spec and the architecture doc list
the limits a 24/7 session still faces: the Android 12+ phantom process
limit and the Android 14+ Task Manager "Stop".
The children file of the main thread lists only the processes that
thread forked. A multi-threaded program such as a gateway can start
subprocesses from other threads, which SIGHUP then missed. Read the
children file of every task, and with the stat-scan fallback look up
only the scanned parent map.
End now signals every process proot traces (TracerPid in
/proc/<pid>/status), except proot and the login shell, so a daemon
that left the shell's tree, such as a gateway respawned under setsid,
also gets the chance to shut down. Each target gets SIGHUP, SIGCONT
and SIGTERM in that order. If any status cannot be read, End falls
back to the login shell's descendants.

The end thread then waits up to 3 s (signalWaitMs) until either the
session ends or every signalled process has exited, polling
Pty.alive(pids), which treats zombies as exited. EOF, kill and
forceKill follow as before. The two tests that stop early use a 2 s
grace so a slow host cannot reach the next step.
The EOF write can block on a full PTY input queue while it holds the
PTY lock. It now runs on a short-lived thread, still guarded by the
lock and the closed flag, so the kill and force-kill steps keep their
timing.

When the reader stops with no exit code, finish() now kills the login
shell first so proot's --kill-on-exit takes its tracees, waits up to
500 ms for the exit code, and only then kills proot.
The wait after signalling rounded the time left down to whole
milliseconds and could stop up to 1 ms early, which made the ordering
test flaky. It now counts in nanoseconds.
A retained session was released in open(), so if it ended between
open() and the new screen's attach, it was dropped and a "session
ended" notice was posted. The retained mark is now cleared in
SessionManager.attach, under the manager's lock, so the session waits
for the screen that opened it.

TerminalActivity.onStop no longer marks its screen hidden when it
stops for a configuration change, which closes the other window in
which a recreation could post the notice.
While the terminal waits for an ending session, it now gives up after
10 s and shows "Couldn't stop the previous session.", mirroring the
Remove wait. Back still returns to the list.

The waiting screen also attaches to the ending session and detaches at
once, so any other screen still showing that session closes.
The notification-shade Task Manager "Stop" arrived in Android 13. The
"Disable child process restrictions" developer option exists on
Android 14+; 12L and 13 need settings_enable_monitor_phantom_procs
and 12 needs device_config.
Linux delivers a pending SIGHUP before SIGTERM, and SIGHUP's default
action exits, so a Python program such as the gateway died before it
could drain on SIGTERM; daemons also often treat HUP as "reload".

End's gentle step now sends SIGTERM, then SIGCONT, to every tracee
except proot and the login shell. TERM goes first so it stays pending
on a stopped job and is handled as soon as CONT resumes it. The 3 s
wait, EOF, kill and forceKill are unchanged.
show() marked the screen hidden when called from onCreate, so a
program exiting before onStart could still post a "session ended"
notice during recreation. It now sets visibility only on the late
path after Ending; from onCreate the screen keeps attach's default
until onStart, which always follows.

The spec now says a retained session is returned by open until a
screen attaches, not once.
The EOF now starts on its own thread, so a 100 ms grace could let a
loaded host record the kill before the EOF. The ordering test and the
blocked EOF test use a 1 s grace.
proot does not set PTRACE_O_EXITKILL, so killing it while a tracee is
stuck (a login shell in D state) detached the survivors; they kept the
PTY slave open, the reader never saw EOF and the session stayed running
for good. forceKill now re-scans the tracees (TracerPid, or the
descendant walk) and SIGKILLs each of them before proot.
It asserted that a constant equals its own value; the signal order is
covered by the End behaviour tests.
A user who left the "Ending the previous session" screen with Home got a
fresh login started out of sight, with a foreground-service start from
the background. The reopen now waits until the activity is started
again.
Between finish() and onStop the screen still counted as visible, so a
program exiting during the close transition posted no notice and the end
was never shown.
With a 100 ms grace a slow CI host could start the EOF thread after the
kill step and fail the assertions; these tests now use a 1 s grace and
the order test a longer wait.
… stop

Design and architecture: End SIGKILLs every tracee before proot, the
reader-error path waits for the exit code before killing the shell, the
service stops itself, and the plan is marked historical.
@oonid
oonid merged commit 369236c into main Oct 18, 2026
1 check passed
@oonid
oonid deleted the feat/background-sessions branch October 18, 2026 07:54
oonid added a commit that referenced this pull request Oct 19, 2026
Decisions from the PR #16 follow-up list: a 10 s drain window with derived Remove and Ending timeouts, JVM tests through small seams for /proc scanning and the service, an app-scoped runner for install, remove and test, and no "0 sessions running" notification.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant