Skip to content

Launch, stop & running-state - #3

Merged
jwinarske merged 9 commits into
mainfrom
launcher
Aug 25, 2026
Merged

Launch, stop & running-state#3
jwinarske merged 9 commits into
mainfrom
launcher

Conversation

@AhmedAdelWafdy7

Copy link
Copy Markdown
Collaborator

Adds the first piece of IVI-launcher support to flatpak_dart: launching an installed app ("tap to open"), stopping it, and listing running instances. Ported from the ivi-homescreen C++ plugin's ApplicationStart / ApplicationStop / find_running_instance.

added flutter examples :

export FLATPAK_NC_LIB="$PWD/build-release/libflatpak_nc.so"
dart run example/launch_app.dart org.gnome.Calculator
dart run example/list_running.dart

I tested the package on a Flutter ivi app.
image
image

@AhmedAdelWafdy7
AhmedAdelWafdy7 force-pushed the launcher branch 2 times, most recently from 92c97ac to 2c22caf Compare July 27, 2026 07:35
@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

2.44%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/include
   flatpak_types.h100%100%100%100%
native/src
   installation_reader.cpp6.80%100%13.16%6.15%100, 102–105, 107–112, 114–115, 117–132, 134–137, 139–140, 142–145, 148–159, 16, 160–163, 165–167, 169, 171, 173–176, 178–179, 18, 180–181, 183–184, 186, 188–189, 19, 190–197, 199, 20, 201–207, 21, 210–219, 22, 220–226, 228–229, 23, 230, 232, 234–235, 237–238, 240–243, 245–247, 249, 25, 250–257, 259, 261–265, 267, 269, 27, 270–272, 276–277, 279, 28, 280–282, 29–30, 301, 303, 305–308, 310–314, 318–319, 32, 320–323, 325–327, 329, 33, 331–333, 335–338, 340–345, 347–348, 350–352, 354–356, 359, 36, 360–361, 363–364, 366–367, 369, 37, 371–372, 374, 377–379, 38, 381–382, 384–389, 39, 390–391, 393–395, 397–403, 405, 407–408, 41, 412–414, 416–417, 419, 42, 420–423, 425–429, 43, 430–439, 44, 441–444, 456–457, 46–47, 476–478, 48, 480–482, 484–486, 488, 49, 490–491, 493, 495–496, 498–499, 50, 500, 502–504, 506, 508–509, 51, 511, 513–514, 516–518, 52, 520–522, 524–526, 53, 55–56, 69–70, 72–75, 77–80, 83, 85–99
   transaction_bridge.cpp0%100%0%0%100–101, 114, 116, 118, 121, 123–131, 133, 136, 139, 143, 148–151, 153, 155–161, 163, 165–169, 17, 171–174, 176, 178, 182–185, 187–189, 19, 190–191, 193–198, 20, 201–203, 205, 207, 21, 210–211, 213–214, 218–219, 22, 220–221, 223–224, 226–228, 23, 232–235, 237–239, 24, 242–244, 246–248, 252–253, 255–259, 26, 265, 267–269, 27, 271–274, 276–277, 279, 28, 280, 282–285, 287–289, 29, 291, 293–296, 298–299, 30, 300, 302, 304–307, 309–311, 313, 315–318, 32, 320–323, 325, 327–329, 33, 330–331, 335–341, 36–39, 41–44, 46–53, 55–66, 70–71, 78, 80–82, 85–92, 98

@jwinarske jwinarske left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. stop() blocks the calling Dart thread for up to ~1.5s per instance (blocking). The reader C ABI is synchronous — FlatpakBindings.readerStop runs the C++ inline on whatever thread calls it, and in a Flutter app that's the UI isolate's thread. The SIGTERM grace loop (15 × usleep(100000), serial across matched instances, plus SIGKILL escalation) means a "close app" tap freezes the homescreen for 1.5s+ whenever an app doesn't exit instantly. The existing reader calls get away with the synchronous pattern because they're fast enumerations; this one deliberately waits. Options: enqueue on a worker (the TransactionWorker serial-queue pattern is already in the codebase), or do SIGTERM immediately, post the sentinel, and run the grace/SIGKILL escalation via g_timeout_add on the bridge's GMainContext thread. The same argument applies to launch()'s installed-refs scan on cold cache, though that's less severe.

  2. DO_NOT_REAP with no reaper leaks zombies (blocking). flatpak_installation_launch_full is called with FLATPAK_LAUNCH_FLAGS_DO_NOT_REAP, the returned FlatpakInstance is discarded, and nothing ever waitpids the bwrap pid. Every launched app leaves a zombie in the embedder process when it exits — for a homescreen that runs for months, that's unbounded. Note that dropping the flag doesn't fix it either: the default reap path installs a child watch on the thread-default main context, and launch() runs on the Dart thread where that's the global default context, which a plain Dart/Flutter-embedder process isn't iterating. The bridge already owns a dedicated GMainContext/GMainLoop thread (flatpak_bridge.cpp), so the clean fix is g_child_watch_add attached to that context with the pid from the returned instance, or a process-wide SIGCHLD reaper if ivi-homescreen doesn't already own one.

  3. Launch result is thrown away. Since you have the FlatpakInstance in hand, post it as a 0x01 FpInstance payload before the sentinel and have launch() return Future. That eliminates the racy Future.delayed(1s) + listRunning() dance the example has to do to learn the instanceId, and it's the pid you need for finding #2 anyway. API change now is cheap; after merge it isn't.

  4. Every error maps to FlatpakNotFoundException. Launch fails for reasons that aren't "not found" — missing runtime, sandbox setup failure, dbus-proxy spawn errors. Callers doing on FlatpakNotFoundException to mean "show install button" will misroute real failures. Add a FlatpakLaunchException (the exceptions.dart sealed hierarchy makes this trivial) or at least distinguish the "app not installed" pre-check from launch_full errors.

Smaller items:

  • Branch/arch resolution asymmetry: if the caller supplies branch but not arch, the resolution loop filters on use_arch (null → no filter) but never checks use_branch, so resolved_arch can come from a ref of a different branch than the one requested. Filter on both when supplied. Also consider flatpak_installation_get_current_installed_app() for the no-hints path instead of scanning all refs — it respects flatpak's own "current" selection rather than first-match order, which matters when multiple branches are installed.
  • stop() semantics: kill(pid, 0) returning EPERM means the process exists but isn't signallable; the code treats it as dead and skips. Within a session installation this is unlikely to matter, but errno != ESRCH should probably still count as found. Also the kill(child_pid, SIGTERM) return value is ignored — found stays true even if the signal failed. And unconditional SIGKILL escalation after 1.5s is a policy decision worth a doc comment or a parameter; the C++ plugin's ApplicationStop didn't escalate.
  • instance_test.dart: the "launch is a method on FlatpakClient" test asserts FlatpakClient.system isA and nothing about launch — it's a no-op. Either drop it or keep only the model tests, which are fine.
  • transaction_bridge.cpp G_CONNECT_DEFAULT → static_cast(0): fine (drops the GLib ≥ 2.74 requirement, presumably for the Yocto/older-distro build), but it's an unrelated drive-by in a commit titled "add flatpak apps lifecycle" — worth a line in the commit message so it doesn't look accidental.
  • Codec: field order in decodeInstance matches the glz::meta tuple, and the roundtrip test covers the C++ side. No cross-language fixture, but that matches the existing structs, so no new debt.
  • CI: pull-requests: write + continue-on-error on the lcov comment step is the right call for fork PRs.

Just need 1-4

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Coverage after merging launcher into main will be

2.35%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/include
   flatpak_types.h100%100%100%100%
native/src
   installation_reader.cpp6.19%100%11.36%5.64%100–101, 104, 106–121, 123–126, 128–133, 135–136, 138–153, 155–158, 160–161, 163–166, 169, 17, 170–184, 186–188, 19, 190, 192, 194–197, 199, 20, 200–202, 204–205, 207, 209, 21, 210–218, 22, 220, 222–228, 23, 231–239, 24, 240–247, 249–251, 253, 255–256, 258–259, 26, 261–264, 266–268, 270–278, 28, 280, 282–286, 288, 29, 290–293, 297–298, 30, 300–303, 31, 322, 324, 326–329, 33, 331–335, 339, 34, 340–344, 346–348, 350, 352–354, 356–359, 361–366, 368–369, 37, 371–373, 375–377, 38, 380–382, 384–385, 387–388, 39, 390, 392–393, 395, 398–399, 40, 400, 403–405, 408–416, 418–419, 42, 420, 423–427, 429, 43, 431–432, 434, 437–439, 44, 440, 442–449, 45, 451–453, 455–457, 460–462, 464–465, 467–469, 47, 470–471, 473–479, 48, 480–487, 489, 49, 490–492, 50, 504–505, 51–52, 524–526, 528–529, 53, 530, 532–534, 536, 538–539, 54, 541, 543–544, 546–548, 550–552, 554, 556–557, 559, 56, 561–562, 564–566, 568–569, 57, 570, 572–574, 58, 60–62, 64–65, 68–72, 75–78, 90–91, 93–96, 98–99
   transaction_bridge.cpp0%100%0%0%100–101, 114, 116, 118, 121, 123–131, 133, 136, 139, 143, 148–151, 153, 155–161, 163, 165–169, 17, 171–174, 176, 178, 182–185, 187–189, 19, 190–191, 193–198, 20, 201–203, 205, 207, 21, 210–211, 213–214, 218–219, 22, 220–221, 223–224, 226–228, 23, 232–235, 237–239, 24, 242–244, 246–248, 252–253, 255–259, 26, 265, 267–269, 27, 271–274, 276–277, 279, 28, 280, 282–285, 287–289, 29, 291, 293–296, 298–299, 30, 300, 302, 304–307, 309–311, 313, 315–318, 32, 320–323, 325, 327–329, 33, 330–331, 335–341, 36–39, 41–44, 46–53, 55–66, 70–71, 78, 80–82, 85–92, 98

@AhmedAdelWafdy7
AhmedAdelWafdy7 force-pushed the launcher branch 2 times, most recently from 07a4890 to 8e47b9f Compare August 6, 2026 16:09
@jwinarske

Copy link
Copy Markdown
Contributor

@AhmedAdelWafdy7

Correct and worth keeping

  • The manual ref-resolution loop (native/src/installation_reader.cpp:360-388) is justified, not redundant: flatpak_installation_launch_fullflatpak_decomposed_new_from_parts defaults a NULL branch to the literal "master" (upstream common/flatpak-ref-utils.c:937-938), so passing NULL through would fail for every stable-branch app.
  • FLATPAK_LAUNCH_FLAGS_DO_NOT_REAP + a dedicated reaper is the right call given there is no GMainLoop to host libflatpak's child watch.
  • stop() signalling child_pid matches upstream flatpak-builtins-kill behavior.

Issues, by severity

1. launch() runs synchronously on the calling isolate thread

flatpak_reader_launch (native/src/installation_reader.cpp:558-561) executes flatpak_installation_launch_full inline: deploy resolution, bwrap argv generation, session-helper D-Bus traffic, and the spawn itself. The repo's own convention puts anything heavier than a read on the TransactionWorker serial queue (native/src/transaction_bridge.cpp:150,341). Launch is the "tap to open" path in an IVI shell — this is exactly where jank lands on the platform isolate.

Fix: route launch through the worker, or a second lightweight worker if launch should not serialize behind a running install transaction. That ordering question needs a decision record either way.

2. Ref resolution diverges from flatpak's own selection on multi-arch / multi-branch installs

The loop takes the first list-order FLATPAK_REF_KIND_APP match (installation_reader.cpp:376-377, break). libflatpak with NULL arch uses flatpak_get_arch(); the CLI resolves multiple branches via the current ref. With e.g. x86_64 + i386, or stable + beta both installed, whichever flatpak_installation_list_installed_refs returns first gets launched.

Fix: prefer flatpak_get_default_arch() matches, and resolve branch via flatpak_installation_get_current_installed_app() before falling back to list order.

3. Error-discriminator inconsistency for "not installed"

Not-installed surfaces as 0x02FlatpakNotFoundException only when the pre-check runs (installation_reader.cpp:381). If the caller supplies both arch and branch, the pre-check is skipped and the same condition returns from launch_full as 0x03FlatpakLaunchException.

Fix: either always run the existence check, or map FLATPAK_ERROR_NOT_INSTALLED from err->code to 0x02 in the launch failure path.

4. PID-reuse race in the SIGKILL escalation

kill(pid, 0) then kill(pid, SIGKILL) 1.5 s later (installation_reader.cpp:425-432) can hit a recycled pid — the target is not this process's descendant, so no zombie holds the slot. Window is small; fix is cheap on target kernels (≥5.3): pidfd_open() at stop() time, pidfd_send_signal() for both TERM and KILL.

Related: reap_thread (installation_reader.cpp:69-77) blocks one thread per launch for the app's entire lifetime. Virtual-memory-only cost, but a single reaper polling pidfds would collapse both mechanisms.

5. Build hook unconditionally clears CFLAGS/CXXFLAGS/LDFLAGS

hook/build.dart:76,83 clears all three for every cmake invocation. Under bitbake/meta-flutter, CFLAGS carries TUNE_CCARGS-derived flags; clearing them silently drops tuning (and any hardening flags) in cross builds while CC/CXX pass through. build_release.sh doing env -u for a host convenience script is fine; the hook is the path Yocto consumers hit.

Fix: preserve the flags in the hook, or make clearing opt-in via a user-define alongside skip_native_build.

Nits

  • lib/src/internal/library_loader.dart:130lastIndexOf(' ') truncates /proc/self/maps paths containing spaces; (deleted)-suffixed lines parse as (deleted) and are skipped, so a deleted-but-mapped Flutter bundle dir is never considered. existsSync guards correctness; parsing column 6 by splitting whitespace-runs from the left (fields 0–4 are fixed) is exact.
  • test/instance_test.dart:44expect(FlatpakClient.system, isA<Function>()) asserts nothing; the comment promises a tear-off type check that isn't there. Add expect(FlatpakClient.user, isA<FlatpakClient Function()>())-style checks or drop the group.
  • native/test/test_types.cpp:97-120 only round-trips glaze↔glaze. The actual contract is C++ glaze → Dart GlazeCodec.decodeInstance (lib/src/ffi/codec.dart:151-163); a golden buffer emitted from the C++ test and checked into test/ would cover the field-order/width coupling that a glz::meta<FpInstance> refactor would silently break.
  • native/include/flatpak_bridge.h:44-46 — comment says launch "posts 0xFF on success or 0x02 on error"; stale. It posts a 0x01 FpInstance payload then 0xFF, and errors are 0x02/0x03 per the discriminator table above it.

Verdict

No correctness blocker on the happy path. Hold merge on #1 and #5; #2/#3 are behavioral contracts cheaper to fix before anything depends on them; #4 and the nits can follow up.

@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

2.74%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/include
   flatpak_types.h100%100%100%100%
native/src
   installation_reader.cpp6.77%100%12%6.19%100, 102–105, 107–110, 113, 115–130, 132–135, 137–142, 144–145, 147–162, 164–167, 169–170, 172–175, 178–193, 195–197, 199, 20, 201, 203–206, 208–211, 213–214, 216, 218–219, 22, 220–227, 229, 23, 231–237, 24, 240–249, 25, 250–256, 258–259, 26, 260, 262, 264–265, 267–268, 27, 270–273, 275–277, 279–287, 289, 29, 291–295, 297, 299–302, 306–307, 309, 31, 310–312, 32–33, 331, 333, 335–338, 34, 340–344, 348–353, 355–357, 359, 36, 362, 364–369, 37, 370–373, 377–378, 380–382, 384–388, 390–391, 393–396, 398–399, 40, 401–404, 406–407, 41, 410–411, 413, 415–419, 42, 420–422, 424, 426–428, 43, 430–433, 435–436, 438–439, 441–442, 446–447, 449, 45, 452–454, 456, 458, 46, 461–463, 466–469, 47, 470–474, 476–478, 48, 482–483, 486–487, 493–498, 50, 500–501, 504–507, 509, 51, 510–516, 518–519, 52, 520, 522–524, 526–528, 53, 531–533, 535–536, 538–539, 54, 540–542, 544–549, 55, 550–558, 56, 560–563, 57, 575–576, 59, 595–597, 599, 60, 600–601, 603–605, 607, 609, 61, 610, 612, 614–615, 617–619, 621–623, 625, 627–628, 63, 630, 632–633, 635–637, 639, 64, 640–641, 643–645, 65, 67–68, 71–75, 78–81, 99
   transaction_bridge.cpp0%100%0%0%100–101, 114, 116, 118, 121, 123–131, 133, 136, 139, 143, 148–151, 153, 155–161, 163, 165–169, 17, 171–174, 176, 178, 182–185, 187–189, 19, 190–191, 193–198, 20, 201–203, 205, 207, 21, 210–211, 213–214, 218–219, 22, 220–221, 223–224, 226–228, 23, 232–235, 237–239, 24, 242–244, 246–248, 252–253, 255–259, 26, 265, 267–269, 27, 271–274, 276–277, 279, 28, 280, 282–285, 287–289, 29, 291, 293–296, 298–299, 30, 300, 302, 304–307, 309–311, 313, 315–318, 32, 320–323, 325, 327–329, 33, 330–331, 335–341, 36–39, 41–44, 46–53, 55–66, 70–71, 78, 80–82, 85–92, 98

@jwinarske

Copy link
Copy Markdown
Contributor

@AhmedAdelWafdy7

Previous findings addressed

This commit resolves four of the five round-1 findings:

Thread-safety premise verified: upstream common/flatpak-installation.c:67-68 documents FlatpakInstallation as threadsafe for concurrent operations from different threads, so launch_impl on the asio thread racing list_apps on the caller thread is fine.

Resolution logic traced: resolve_launch_target (native/src/installation_reader.cpp:359-410) is correct through the multi-arch/multi-branch cases — current-ref-first with hint filters, default-arch preference, and the early break (the continues guard it from firing on filtered entries).

New issues introduced by this commit

1. Destructor drops queued launches, hanging Dart futures

~InstallationReader (native/include/installation_reader.h:34-36, .cpp:90-95) calls launch_io_.stop() then joins. io_context::stop() lets the currently executing handler finish but abandons everything still queued — those handlers never run, their Dart_Port never receives 0x02/0x03/0xFF, the Completer in lib/src/installation.dart never resolves, and the ReceivePort leaks. Any await client.launch() racing client.close() hangs forever.

Fix: drain instead of stop — launch_work_guard_.reset() then join(), so run() returns only after the queue empties. A wedged launch_full blocking close() is the trade-off; document it. Silently hanging a future is worse.

2. pidfd_open failure conflated with ESRCH

stop() (installation_reader.cpp:521-524) treats any pidfd < 0 as "no live app process". On a kernel without SYS_pidfd_open (<5.3) that is ENOSYS for every instance → stop() reports "no running instance" while apps are demonstrably running. Named targets are all ≥5.10, so this may be a documented kernel floor rather than a live code path — but at minimum check errno == ESRCH before continue, and either fall back to the plain kill() path or fail loudly on ENOSYS.

3. EINTR loses the SIGKILL escalation

stop_escalate_thread (installation_reader.cpp:493-501): poll() returning −1 on EINTR skips the ret == 0 branch, closes the fd, and exits — a SIGTERM-ignoring app survives. Retry with do { ... } while (ret < 0 && errno == EINTR) (g_poll does not retry either). Recomputing the deadline across retries is technically correct but irrelevant at 1.5 s.

Nits

  • When the app is installed but hints exclude every ref, the error is still "app not installed""no installed ref matches requested arch/branch" saves a confused debugging session.

Carried over from round 1, still open

  • Stale native/include/flatpak_bridge.h:44-46 comment — launch posts a 0x01 FpInstance payload then 0xFF, not bare 0xFF.
  • Vacuous assertion at test/instance_test.dart:44 (expect(FlatpakClient.system, isA<Function>())).
  • No cross-language golden test for C++ glaze → Dart GlazeCodec.decodeInstance.
  • /proc/self/maps parsing in lib/src/internal/library_loader.dart:130 — space-containing paths truncated, (deleted) lines skipped.

Verdict

The substantive follow-up is good work. Fix #1 (destructor drain) before merge — it is a real hang. #2/#3 are small patches. The asio question deserves a deliberate decision.

@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

2.71%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/include
   flatpak_types.h100%100%100%100%
native/src
   installation_reader.cpp6.59%100%12%6.01%101–102, 104–107, 109–112, 115, 117–132, 134–137, 139–144, 146–147, 149–164, 166–169, 171–172, 174–177, 180–195, 197–199, 201, 203, 205–208, 210–213, 215–216, 218, 22, 220–229, 231, 233–239, 24, 242–249, 25, 250–258, 26, 260–262, 264, 266–267, 269, 27, 270, 272–275, 277–279, 28, 281–289, 29, 291, 293–297, 299, 301–304, 308–309, 31, 311–314, 33, 333, 335, 337–339, 34, 340, 342–346, 35, 350–355, 357–359, 36, 361, 364, 366–375, 379, 38, 380, 382–384, 386–389, 39, 390, 392–393, 395–398, 400–401, 403–406, 408–409, 412–413, 415, 417–419, 42, 420–424, 426, 428–429, 43, 430, 432–435, 437–438, 44, 440–441, 443–444, 448–449, 45, 451, 454–456, 458, 460, 463–465, 468–469, 47, 470–476, 478–479, 48, 480, 484–485, 488–489, 49, 495–499, 50, 500, 502–507, 510–511, 513–515, 517–518, 52, 521–524, 526–529, 53, 530–533, 535–537, 539, 54, 540–543, 546, 548–549, 55, 550, 553–555, 557–558, 56, 560–564, 566–569, 57, 570–579, 58, 580, 582–585, 59, 597–598, 61, 617–619, 62, 621–623, 625–627, 629, 63, 631–632, 634, 636–637, 639–641, 643–645, 647, 649, 65, 650, 652, 654–655, 657–659, 66, 661–663, 665–667, 67, 69–70, 73–77, 80–83
   transaction_bridge.cpp0%100%0%0%100–101, 114, 116, 118, 121, 123–131, 133, 136, 139, 143, 148–151, 153, 155–161, 163, 165–169, 17, 171–174, 176, 178, 182–185, 187–189, 19, 190–191, 193–198, 20, 201–203, 205, 207, 21, 210–211, 213–214, 218–219, 22, 220–221, 223–224, 226–228, 23, 232–235, 237–239, 24, 242–244, 246–248, 252–253, 255–259, 26, 265, 267–269, 27, 271–274, 276–277, 279, 28, 280, 282–285, 287–289, 29, 291, 293–296, 298–299, 30, 300, 302, 304–307, 309–311, 313, 315–318, 32, 320–323, 325, 327–329, 33, 330–331, 335–341, 36–39, 41–44, 46–53, 55–66, 70–71, 78, 80–82, 85–92, 98

@jwinarske

Copy link
Copy Markdown
Contributor

@AhmedAdelWafdy7 Needs a rebase. Baseline package published: https://pub.dev/packages/flatpak_dart

@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

27.42%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/src
   installation_reader.cpp3.28%1.44%12.77%6.31%100, 100, 100–101, 101, 101–102, 102, 102–103, 103, 103, 103, 103–104, 104, 104, 104, 104–105, 105, 105, 105, 105–106, 106, 106, 106, 106, 108, 108, 108–110, 110, 110–111, 111, 111, 111, 111, 111, 111, 111, 111, 113–115, 115, 115–116, 116, 116–117, 117, 117–118, 120, 120, 120–121, 123–124, 124, 124, 124, 124–125, 125, 125, 125, 125–126, 126, 126, 126, 126–127, 127, 127, 127, 127–128, 128, 128, 128, 128–129, 129, 129, 129, 129–130, 130, 130, 130, 130–131, 131, 131–132, 132, 132, 132, 132–133, 133, 133, 133, 133–134, 134, 134–135, 135, 135–136, 136, 136–137, 137, 137–138, 138, 138, 140, 140, 140–142, 142, 142–143, 143, 143, 143, 143, 143, 143, 143, 143, 145–146, 148, 148, 148–149, 149, 149–150, 150, 150–151, 154–155, 155, 155, 155, 155–156, 156, 156, 156, 156–157, 157, 157, 157, 157–158, 158, 158, 158, 158–159, 159, 159, 159, 159–160, 160, 160, 160, 160–161, 161, 161, 161, 161–162, 162, 162–163, 163, 163, 163, 163–164, 164, 164, 164, 164–165, 165, 165–166, 166, 166–167, 167, 167–168, 168, 168–169, 169, 169, 171, 171, 171–172, 172, 172–173, 173, 173, 173, 173, 173, 173, 173, 173, 175, 177, 179, 179, 179–180, 180, 180–181, 181, 181–182, 184, 184, 184–186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186–187, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189–190, 192, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194–195, 195, 195, 195, 195, 195, 195, 195, 195–196, 196, 196, 196, 196, 196, 196, 196, 196–197, 197, 197, 197, 197, 197, 197, 197, 197–198, 198, 198, 198, 198, 198, 198, 198, 198–199, 199, 199, 199, 199, 199, 199, 199, 199–200, 200, 200–202, 202, 202–203, 203, 203, 203, 203, 203, 203, 203, 203, 205, 207–208, 208, 208, 208, 208, 208, 208–209, 209, 209–210, 210, 210–211, 211, 211–212, 212, 212–213, 216–217, 217, 217–218, 218, 218, 218, 218, 218, 218, 218, 218–219, 219, 219, 219, 219, 219, 219, 219, 219–220, 220, 220, 220, 220, 220, 220, 220, 220–221, 221, 221, 221, 221, 221, 221, 221, 221–222, 222, 222, 222, 222, 222, 222, 222, 222–223, 223, 223, 223, 223–224, 224, 224, 224, 224–225, 225, 225, 225, 225–226, 226, 226–227, 227, 227–228, 228, 228–229, 229, 229, 229, 229–230, 230, 230, 230, 230–231, 231, 231, 231, 231–232, 232, 232, 232, 232, 234, 234, 234–235, 235, 235–236, 236, 236, 236, 236, 236, 236, 236, 236, 238, 24, 240–241, 243–244, 246, 246, 246–247, 247, 247–248, 248, 248–249, 25, 25, 25, 251, 251, 251–253, 255, 255, 255, 255, 255, 255, 255, 255, 255

@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

27.42%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/src
   installation_reader.cpp3.28%1.44%12.77%6.31%100, 100, 100–101, 101, 101–102, 102, 102–103, 103, 103, 103, 103–104, 104, 104, 104, 104–105, 105, 105, 105, 105–106, 106, 106, 106, 106, 108, 108, 108–110, 110, 110–111, 111, 111, 111, 111, 111, 111, 111, 111, 113–115, 115, 115–116, 116, 116–117, 117, 117–118, 120, 120, 120–121, 123–124, 124, 124, 124, 124–125, 125, 125, 125, 125–126, 126, 126, 126, 126–127, 127, 127, 127, 127–128, 128, 128, 128, 128–129, 129, 129, 129, 129–130, 130, 130, 130, 130–131, 131, 131–132, 132, 132, 132, 132–133, 133, 133, 133, 133–134, 134, 134–135, 135, 135–136, 136, 136–137, 137, 137–138, 138, 138, 140, 140, 140–142, 142, 142–143, 143, 143, 143, 143, 143, 143, 143, 143, 145–146, 148, 148, 148–149, 149, 149–150, 150, 150–151, 154–155, 155, 155, 155, 155–156, 156, 156, 156, 156–157, 157, 157, 157, 157–158, 158, 158, 158, 158–159, 159, 159, 159, 159–160, 160, 160, 160, 160–161, 161, 161, 161, 161–162, 162, 162–163, 163, 163, 163, 163–164, 164, 164, 164, 164–165, 165, 165–166, 166, 166–167, 167, 167–168, 168, 168–169, 169, 169, 171, 171, 171–172, 172, 172–173, 173, 173, 173, 173, 173, 173, 173, 173, 175, 177, 179, 179, 179–180, 180, 180–181, 181, 181–182, 184, 184, 184–186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186–187, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189, 189–190, 192, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, 194–195, 195, 195, 195, 195, 195, 195, 195, 195–196, 196, 196, 196, 196, 196, 196, 196, 196–197, 197, 197, 197, 197, 197, 197, 197, 197–198, 198, 198, 198, 198, 198, 198, 198, 198–199, 199, 199, 199, 199, 199, 199, 199, 199–200, 200, 200–202, 202, 202–203, 203, 203, 203, 203, 203, 203, 203, 203, 205, 207–208, 208, 208, 208, 208, 208, 208–209, 209, 209–210, 210, 210–211, 211, 211–212, 212, 212–213, 216–217, 217, 217–218, 218, 218, 218, 218, 218, 218, 218, 218–219, 219, 219, 219, 219, 219, 219, 219, 219–220, 220, 220, 220, 220, 220, 220, 220, 220–221, 221, 221, 221, 221, 221, 221, 221, 221–222, 222, 222, 222, 222, 222, 222, 222, 222–223, 223, 223, 223, 223–224, 224, 224, 224, 224–225, 225, 225, 225, 225–226, 226, 226–227, 227, 227–228, 228, 228–229, 229, 229, 229, 229–230, 230, 230, 230, 230–231, 231, 231, 231, 231–232, 232, 232, 232, 232, 234, 234, 234–235, 235, 235–236, 236, 236, 236, 236, 236, 236, 236, 236, 238, 24, 240–241, 243–244, 246, 246, 246–247, 247, 247–248, 248, 248–249, 25, 25, 25, 251, 251, 251–253, 255, 255, 255, 255, 255, 255, 255, 255, 255

@AhmedAdelWafdy7

Copy link
Copy Markdown
Collaborator Author

@jwinarske, the rebase is complete!

@jwinarske

jwinarske commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Pushed four follow-up commits (cea4969, e04d9b2, d65fffa, 28eb914) addressing the review.

asio is gone — no downstream dependency needed

The review flagged that <asio.hpp> was included but never wired into native/CMakeLists.txt, so configure succeeded and compile failed cold for anyone building from source. Rather than add a CMake existence check, I dropped asio entirely: TransactionWorker already has exactly the shape the launch queue needed (std::thread + mutex + condvar + queue), so the launch queue now mirrors it.

This means the DEPENDS += asio note for the meta-flutter recipe is moot — no new native dependency is introduced by this PR. libasio-dev/asio-devel have also been removed from all four distro jobs in ci.yml and from coverage.yml, so this is the first run exercising those edited install lines.

Review items

All "must fix" and "should fix" items are addressed, plus the nits. Header doc drift corrected (launch_full + DO_NOT_REAP, the 0x02/0x03 split, and stop signalling the child pid rather than bwrap). The no-op launch is a method on FlatpakClient test is replaced with real compile-time signature checks, and the FpInstance wire format is now pinned by a golden buffer emitted by the C++ writer and decoded by GlazeCodec — the same gap still exists for the other types. reap_async carries a TODO for the single-epoll-reaper design.

Two things surfaced while testing that went beyond the review:

  • childPid was always 0 on the instance launch() returns. libflatpak caches it when it constructs the FlatpakInstance, before bwrap has written the pid file. launch_impl now re-reads it (bounded, 500ms, geometric backoff), so launch() returns the real child pid.
  • stop() could report "no running instance" for an app that is running. child_pid <= 0 dropped the instance while found stayed false, so the caller got FlatpakNotFoundException and the app was never signalled. Now falls back to the outer bwrap pid, and "matched but could not signal" reports as a new FlatpakStopException rather than a not-found.

Also hardened: waitpid retries on EINTR (an unrestarted call would leak the bwrap child as a zombie — the Dart VM delivers SIGPROF); the non-pidfd fallback now escalates to SIGKILL (measured: GTK apps ignore SIGTERM and exit only on the escalation, so a TERM-only fallback silently fails rather than degrading); and stop() verifies process identity via /proc/<pid>/stat start time, since pidfd pins a process only after the open and the pid came off disk.

Verification

ASan/UBSan 4/4 · TSAN clean of our code (the 9 reports are GLib/GDBus internals — main shows the same) · 72/72 Dart · 4/4 ctest · -Wall -Wextra clean · clang-tidy-19 clean across all four sources · clang-format clean · both examples exercised end to end against org.gnome.Calculator.

@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

27.76%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/src
   installation_reader.cpp4.24%2.12%13.46%7.68%104–105, 105, 105, 105–106, 111, 113–115, 115, 115–116, 120–123, 123, 123, 125, 128–129, 131, 131, 131–132, 132, 132–133, 133, 133–134, 136, 136, 136–138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138–139, 142, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144–145, 145, 145, 145, 145, 145, 145, 145, 145–146, 146, 146, 146, 146, 146, 146, 146, 146–147, 147, 147, 147, 147, 147, 147, 147, 147–148, 148, 148, 148, 148, 148, 148, 148, 148–149, 149, 149, 149, 149, 149, 149, 149, 149–150, 150, 150, 150, 150–151, 151, 151, 151, 151–152, 152, 152, 152, 152–153, 153, 153–154, 154, 154–155, 155, 155–156, 156, 156, 156, 156–157, 157, 157, 157, 157–158, 158, 158, 158, 158–159, 159, 159, 159, 159, 161, 161, 161–163, 163, 163–164, 164, 164, 164, 164, 164, 164, 164, 164, 166–168, 168, 168–169, 169, 169–170, 170, 170–171, 173, 173, 173–174, 176–177, 177, 177, 177, 177–178, 178, 178, 178, 178–179, 179, 179, 179, 179–180, 180, 180, 180, 180–181, 181, 181, 181, 181–182, 182, 182, 182, 182–183, 183, 183, 183, 183–184, 184, 184–185, 185, 185, 185, 185–186, 186, 186, 186, 186–187, 187, 187–188, 188, 188–189, 189, 189–190, 190, 190–191, 191, 191, 193, 193, 193–195, 195, 195–196, 196, 196, 196, 196, 196, 196, 196, 196, 198–199, 201, 201, 201–202, 202, 202–203, 203, 203–204, 207–208, 208, 208, 208, 208–209, 209, 209, 209, 209–210, 210, 210, 210, 210–211, 211, 211, 211, 211–212, 212, 212, 212, 212–213, 213, 213, 213, 213–214, 214, 214, 214, 214–215, 215, 215–216, 216, 216, 216, 216–217, 217, 217, 217, 217–218, 218, 218–219, 219, 219–220, 220, 220–221, 221, 221–222, 222, 222, 224, 224, 224–225, 225, 225–226, 226, 226, 226, 226, 226, 226, 226, 226, 228, 230, 232, 232, 232–233, 233, 233–234, 234, 234–235, 237, 237, 237–239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239–240, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242–243, 245, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247–248, 248, 248, 248, 248, 248, 248, 248, 248–249, 249, 249, 249, 249, 249, 249, 249, 249–250, 250, 250, 250, 250, 250, 250, 250, 250–251, 251, 251, 251, 251, 251, 251, 251, 251–252, 252, 252, 252, 252, 252, 252, 252, 252–253, 253, 253–255, 255, 255–256, 256, 256, 256, 256, 256, 256, 256, 256, 258, 260–261, 261, 261, 261, 261, 261, 261–262, 262, 262–263, 263, 263–264, 264, 264–265, 265, 265–266, 269, 27, 270, 270, 270–271, 271, 271, 271, 271, 271, 271, 271, 271–272, 272, 272, 272, 272, 272,

Drop asio in favour of the serial-queue pattern already used by
TransactionWorker (std::thread + mutex + condvar + queue). asio was
pulling in a system dependency for a single one-op queue that had to be
satisfied by four CI distros and every consumer sysroot, and it was
never wired into native/CMakeLists.txt — configure succeeded and compile
failed cold for anyone building from source. Removing it also means no
`DEPENDS += asio` is needed in the meta-flutter recipe.

Destruction order is preserved: drain the queue, join, then unref, so a
launch already queued still runs against a live installation_.

Fix header doc drift in flatpak_bridge.h: launch uses launch_full() with
DO_NOT_REAP and posts 0x01 FpInstance + 0xFF on success, 0x02 for
not-installed and 0x03 for launch failure; stop signals the sandboxed
app process (child pid), not the outer bwrap pid.

Also:
- TODO on reap_async: one parked thread per running app should become a
  single epoll'd reaper multiplexing pidfds.
- Document that stop() is host-wide (flatpak instances are not scoped to
  an installation) on both FlatpakClient.stop and Installation.stop.
- Comment the non-pidfd kill() fallback as intentionally un-escalated.
- Replace the no-op "launch is a method" test with real compile-time
  signature checks, and pin the FpInstance wire format with a golden
  buffer emitted by the C++ writer and decoded by GlazeCodec.
- Correct the grace period in example/launch_app.dart (1.5s, not 2s).
- Note the defensive 0x02 branch in listRunning.
- Restore the section divider rule style in installation_reader.cpp.

Signed-off-by: Joel Winarske <joel.winarske@linux.com>
flatpak_instance_get_child_pid() returns whatever the instance directory
held when the FlatpakInstance was constructed, and launch_full() builds
that object before bwrap has written the pid file — so it always read 0,
which made the launch dartdoc's promise that callers get the instance
details without polling listRunning() true only for instanceId and pid.

Re-enumerate flatpak_instance_get_all() on the launch thread, matching
the instance id, until a freshly constructed object carries the real
pid. libflatpak 1.18.1 exposes no flatpak_instance_new_for_id(), so
get_all() is the only way to force the re-read.

Bounded at 500ms in 5ms polls, and bails early once the instance has
appeared and then vanished, so an app that exits before bwrap publishes
the pid cannot park the launch thread. Timing out still yields 0, which
is what callers saw before, so this is strictly an improvement rather
than a new failure mode. Measured cost on the normal path is nil:
launch() round-trips in ~55ms with or without the re-read.

Document childPid as best-effort on FlatpakClient.launch,
Installation.launch and the FlatpakInstance.childPid field.

Verified end to end against org.gnome.Calculator: launch() now returns
childPid=3453764, matching what listRunning() reports for the same
instance id.

Signed-off-by: Joel Winarske <joel.winarske@linux.com>
Addresses the reliability, security and performance findings from review
of the launcher branch.

Reliability:

R1 stop() no longer reports "no running instance" for an app that is
   running. child_pid <= 0 previously dropped the instance while `found`
   stayed false, so the caller got FlatpakNotFoundException and the app
   was never signalled. Now falls back to the outer bwrap pid, and tracks
   matched separately from signalled so "matched but could not signal"
   reports as 0x03 / FlatpakStopException instead of a not-found.

R2 reap_thread() retries waitpid() on EINTR. A single unrestarted call
   would abandon the bwrap child as a zombie for the lifetime of the host
   process — the exact leak DO_NOT_REAP + the reaper exist to prevent.
   The Dart VM delivers SIGPROF; embedders install their own handlers.

R3 resolve_launch_target() propagates the list_installed_refs GError
   rather than discarding it. An unreadable installation reported as
   "app not installed" sends callers after the wrong problem.

R4 The no-pidfd fallback now escalates to SIGKILL. Measured: GTK apps
   ignore SIGTERM and exit only on the escalation, so a TERM-only
   fallback would silently fail to stop them on a pre-5.3 kernel.

R5 Documented why installation_ needs no mutex (libflatpak documents
   FlatpakInstallation as concurrency-safe) now that launch_impl touches
   it from the launch thread. The invariant was previously an undocumented
   assumption after the single-thread comment was dropped.

Security:

S1 stop() skips instances whose process has already exited, and verifies
   process identity via /proc/<pid>/stat field 22 (start time) before
   signalling. pidfd pins a process only after the open; the pid came off
   disk and could have been recycled in between, which would have sent
   SIGTERM and then SIGKILL to an unrelated process the user owns. The
   fallback path re-checks identity before the kill for the same reason.

Performance:

P1 reread_child_pid() backs off geometrically (1,2,4..64ms) instead of
   polling every 5ms. Each probe re-parses the info file of every running
   flatpak on the host; this covers the same 500ms in ~13 probes rather
   than ~100, with the common path (found on the first probe) unchanged.

P2 Shutdown cancels the queued launch backlog instead of draining it, so
   close() costs at most the in-flight launch. Each cancelled request
   still gets an error frame, so no Dart future hangs. Measured: close()
   with 5 queued went from ~5 sequential sandbox spawns to 53ms, with
   1 launched and 4 cancelled cleanly.

P3 Documented why stop()/list_running() stay on the Dart thread rather
   than joining the launch queue: measured at ~102us (3 instances) to
   ~161us (9) for list_running and ~727us for a stop matching 6, well
   inside a frame, and queueing them would make stop() wait on an
   in-flight sandbox spawn.

Verified: ASan/UBSan 4/4, TSAN clean of our code (9 GLib/GDBus reports,
same baseline as main), 72/72 Dart, -Wall -Wextra clean, launch latency
unchanged at ~47-64ms, and stop() latency A/B'd against the previous
commit (1.6-2.0s settled, ~4s mid-startup) — identical either side.

Signed-off-by: Joel Winarske <joel.winarske@linux.com>
bugprone-implicit-widening-of-multiplication-result: both sleeps computed
int * int and let the result widen to g_usleep's gulong parameter. Values
are small enough that this could not overflow in practice, but the cast
makes the arithmetic happen in the destination type.

clang-tidy-19 is now clean across all four native sources with the
project's .clang-tidy config, and clang-format reports every file
correctly formatted.

Signed-off-by: Joel Winarske <joel.winarske@linux.com>
@github-actions

Copy link
Copy Markdown

Coverage after merging launcher into main will be

27.76%

Coverage Report for Changed Files
FileStmtsBranchesFuncsLinesUncovered Lines
native/src
   installation_reader.cpp4.24%2.12%13.46%7.68%104–105, 105, 105, 105–106, 111, 113–115, 115, 115–116, 120–123, 123, 123, 125, 128–129, 131, 131, 131–132, 132, 132–133, 133, 133–134, 136, 136, 136–138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138–139, 142, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144–145, 145, 145, 145, 145, 145, 145, 145, 145–146, 146, 146, 146, 146, 146, 146, 146, 146–147, 147, 147, 147, 147, 147, 147, 147, 147–148, 148, 148, 148, 148, 148, 148, 148, 148–149, 149, 149, 149, 149, 149, 149, 149, 149–150, 150, 150, 150, 150–151, 151, 151, 151, 151–152, 152, 152, 152, 152–153, 153, 153–154, 154, 154–155, 155, 155–156, 156, 156, 156, 156–157, 157, 157, 157, 157–158, 158, 158, 158, 158–159, 159, 159, 159, 159, 161, 161, 161–163, 163, 163–164, 164, 164, 164, 164, 164, 164, 164, 164, 166–168, 168, 168–169, 169, 169–170, 170, 170–171, 173, 173, 173–174, 176–177, 177, 177, 177, 177–178, 178, 178, 178, 178–179, 179, 179, 179, 179–180, 180, 180, 180, 180–181, 181, 181, 181, 181–182, 182, 182, 182, 182–183, 183, 183, 183, 183–184, 184, 184–185, 185, 185, 185, 185–186, 186, 186, 186, 186–187, 187, 187–188, 188, 188–189, 189, 189–190, 190, 190–191, 191, 191, 193, 193, 193–195, 195, 195–196, 196, 196, 196, 196, 196, 196, 196, 196, 198–199, 201, 201, 201–202, 202, 202–203, 203, 203–204, 207–208, 208, 208, 208, 208–209, 209, 209, 209, 209–210, 210, 210, 210, 210–211, 211, 211, 211, 211–212, 212, 212, 212, 212–213, 213, 213, 213, 213–214, 214, 214, 214, 214–215, 215, 215–216, 216, 216, 216, 216–217, 217, 217, 217, 217–218, 218, 218–219, 219, 219–220, 220, 220–221, 221, 221–222, 222, 222, 224, 224, 224–225, 225, 225–226, 226, 226, 226, 226, 226, 226, 226, 226, 228, 230, 232, 232, 232–233, 233, 233–234, 234, 234–235, 237, 237, 237–239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239–240, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242, 242–243, 245, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247–248, 248, 248, 248, 248, 248, 248, 248, 248–249, 249, 249, 249, 249, 249, 249, 249, 249–250, 250, 250, 250, 250, 250, 250, 250, 250–251, 251, 251, 251, 251, 251, 251, 251, 251–252, 252, 252, 252, 252, 252, 252, 252, 252–253, 253, 253–255, 255, 255–256, 256, 256, 256, 256, 256, 256, 256, 256, 258, 260–261, 261, 261, 261, 261, 261, 261–262, 262, 262–263, 263, 263–264, 264, 264–265, 265, 265–266, 269, 27, 270, 270, 270–271, 271, 271, 271, 271, 271, 271, 271, 271–272, 272, 272, 272, 272, 272,

@jwinarske
jwinarske merged commit 3e92f3f into main Aug 25, 2026
7 checks passed
@jwinarske
jwinarske deleted the launcher branch August 25, 2026 15:53
@jwinarske jwinarske mentioned this pull request Aug 25, 2026
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.

2 participants