Skip to content

extension/service: handle concurrent .start() calls and cancellation#1535

Draft
amezin wants to merge 11 commits into
masterfrom
service
Draft

extension/service: handle concurrent .start() calls and cancellation#1535
amezin wants to merge 11 commits into
masterfrom
service

Conversation

@amezin
Copy link
Copy Markdown
Member

@amezin amezin commented Oct 8, 2025

Summary by CodeRabbit

Release Notes

  • Improvements

    • Enhanced extension installation and service configuration with improved app information tracking
    • Improved subprocess logging with asynchronous journald collection for better reliability
    • Streamlined error handling with robust cancellation support to prevent resource leaks
    • Simplified subprocess environment variable management
  • Bug Fixes

    • Fixed redundant installation steps during extension initialization

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

The changes refactor the ddterm shell to extract promise/timeout utilities into a shared module, transition from executable paths to app-info objects throughout service and installation layers, and restructure subprocess log collection to support journald and streaming I/O asynchronously.

Changes

Cohort / File(s) Summary
Promise utility extraction
ddterm/util/promise.js, ddterm/util/meson.build
New utility module exports three helpers: promisify(), wait_timeout(), and wait_property() for async operations. Build config updated to include promise.js in util source set.
AppControl refactoring
ddterm/shell/appcontrol.js
Removed internal GLib-based timeout and property-wait implementations; replaced with imports from ../util/promise.js. Removes GLib dependency.
Extension installation and service setup
ddterm/shell/extension.js
Updated install() to capture and return app_info. Modified Service configuration to use app_info instead of executable: launcher_path. Replaced deprecated log_collector usage with service.subprocess?.get_logs()(). Removed redundant install call.
Installation and app-info management
ddterm/shell/install.js
Updated public method signatures: File.install() and File.get_existing_content() now return objects with metadata; Installer.install() now returns app_info via DesktopAppInfo instead of no return value. Uses returned flags to conditionally trigger reload.
Service property and subprocess launch
ddterm/shell/service.js
Replaced executable property with app-info (type: Gio.AppInfo). Reworked subprocess creation to derive commandline from app-info, use launch context for environment, emit launch-started, and handle pid via VariantDict. Replaced direct wait logic with explicit #wait_subprocess() using cancellable and promise-based wait mechanisms.
Subprocess log collection
ddterm/shell/subprocess.js
Replaced synchronous JournalctlLogCollector with asynchronous collect_journald_logs() and collect_stdio_logs(). Simplified environment handling to use subprocess_launcher.set_environ(). Unified log retrieval via _get_logs() mechanism. Added public accessors get_pid() and get_logs(). Reworked async utilities to use generic promisify helper.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 App-info hops where executables once ran,
Promise utilities hop in their plan,
Journald logs stream with asynchronous care,
Shell refactored with elegant flair! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: handling concurrent .start() calls and cancellation in the extension/service components, which aligns with the core changes refactoring async/await patterns and subprocess management.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch service

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad1c2ab and 3953fc8.

📒 Files selected for processing (7)
  • ddterm/shell/appcontrol.js
  • ddterm/shell/extension.js
  • ddterm/shell/install.js
  • ddterm/shell/service.js
  • ddterm/shell/subprocess.js
  • ddterm/util/meson.build
  • ddterm/util/promise.js
🧰 Additional context used
🧬 Code graph analysis (3)
ddterm/shell/subprocess.js (1)
ddterm/util/promise.js (1)
  • promisify (8-21)
ddterm/util/promise.js (1)
ddterm/shell/appcontrol.js (2)
  • cancellable (98-98)
  • cancellable (119-119)
ddterm/shell/service.js (1)
ddterm/shell/subprocess.js (4)
  • argv (64-71)
  • proc (76-79)
  • Subprocess (161-266)
  • Subprocess (161-266)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: ci / test (alpine-3.21)
  • GitHub Check: ci / test (ubuntu-24.04)
  • GitHub Check: ci / test (debian-13)
  • GitHub Check: ci / test (opensuse-tumbleweed)
  • GitHub Check: ci / test (ubuntu-25.10)
  • GitHub Check: ci / test (fedora-43)
  • GitHub Check: ci / test (ubuntu-25.04)
  • GitHub Check: ci / test (fedora-42)
  • GitHub Check: ci / test (alpine-3.20)
  • GitHub Check: ci / test (centos-10)
  • GitHub Check: ci / test (alpine-3.23)
  • GitHub Check: ci / test (alpine-3.22)
  • GitHub Check: ci / archlinux-package
  • GitHub Check: ci / update-translations
🔇 Additional comments (23)
ddterm/util/meson.build (1)

6-6: LGTM!

The build configuration correctly includes the new promise.js utility module alongside the existing displayconfig.js.

ddterm/shell/appcontrol.js (1)

13-13: LGTM!

Clean refactoring to use the shared promise utilities. This eliminates code duplication and centralizes timeout and property-wait logic.

ddterm/util/promise.js (3)

8-21: LGTM!

The promisify helper correctly implements the GIO async/finish pattern, preserving this context and properly catching errors from the finish callback.


23-49: LGTM!

The wait_timeout implementation correctly handles both timeout and cancellation scenarios with proper cleanup. The design where it always throws (either CANCELLED or TIMED_OUT) is appropriate for use with Promise.race() patterns.


51-87: LGTM!

The wait_property implementation correctly handles:

  • Early return when predicate is already satisfied
  • Signal-based property watching with proper cleanup
  • Error propagation from predicate evaluation
  • Cancellation support with proper cleanup

The captured value ensures consistency between predicate check and return value.

ddterm/shell/install.js (3)

10-22: LGTM!

Good backward-compatible handling of the GioUnix namespace split introduced in GLib 2.79.2. The fallback to Gio.DesktopAppInfo ensures the code works on older GLib versions.


40-76: LGTM!

The enhanced return types from get_existing_content() and install() provide necessary information for callers to make conditional decisions (e.g., DBus reload only when changed, and returning the correct filename for DesktopAppInfo).


128-149: LGTM!

The refactored install() method:

  • Correctly captures install results to conditionally trigger DBus reload
  • Creates app_info from the effective desktop entry file (whether newly installed or existing)
  • Returns app_info for downstream consumption by Service

This is a cleaner design that avoids unnecessary DBus reload calls.

ddterm/shell/extension.js (3)

137-166: LGTM!

The install() function now correctly returns app_info for use by the Service configuration, enabling the transition from executable path to a proper Gio.AppInfo object.


240-256: LGTM!

The Service is now configured with app_info instead of executable, aligning with the updated Service API and enabling proper app launch semantics.


289-298: LGTM!

Proper error handling for log collection:

  • Gracefully handles missing subprocess
  • Shows error notification with logs when available
  • Falls back to notification without logs if log collection fails
ddterm/shell/subprocess.js (6)

63-85: LGTM!

The collect_journald_logs function correctly builds and executes a journalctl query to retrieve recent logs, using proper timestamp formatting and PID filtering.


87-104: LGTM!

Clean async generator implementation for streaming reads. The finally block ensures proper stream cleanup regardless of how iteration ends.


106-121: LGTM!

The generator correctly splits arrays while preserving delimiters, which is essential for accurate line counting in collect_stdio_logs.


123-159: LGTM!

The collect_stdio_logs function implements a proper tee-style log collection:

  • Streams output to stderr for real-time visibility
  • Maintains a rolling buffer of the last 50 lines
  • Correctly handles partial lines across chunk boundaries

203-215: LGTM!

The simplified environment handling with set_environ and the unified log collection setup are clean improvements. The differentiated handling for journald (lazy function) vs stdio (eager promise) correctly reflects the different collection strategies.


233-260: LGTM!

Clean promisified implementations of wait() and wait_check(). The get_pid() and get_logs() accessors provide proper encapsulation for the subprocess identification and log retrieval functionality.

ddterm/shell/service.js (6)

5-10: LGTM!

Required imports for the new functionality: GLib for shell_parse_argv and wait_property for reactive property monitoring.


28-34: LGTM!

Property type change from string (executable) to Gio.AppInfo (app-info) enables richer app launch semantics and aligns with the Installer.install() return type.


122-124: LGTM!

Passing both name and owner to the update handler enables more informative logging.


172-206: LGTM!

The #create_subprocess method correctly:

  • Parses the commandline from app_info
  • Configures the launch context with extra environment variables
  • Emits proper launch-started and launched signals with platform data
  • Creates the appropriate subprocess type for the display backend

222-235: LGTM!

The enhanced logging now includes the bus name, making it easier to debug name ownership changes.


237-290: Well-designed concurrent start handling!

The reworked start() method properly handles:

  1. Concurrent calls: The while (this.starting) loop with wait_property ensures subsequent callers wait for an in-progress startup
  2. Early exit: Returns immediately if already registered
  3. Subprocess lifecycle: The Promise.race pattern correctly detects subprocess termination before D-Bus registration
  4. Cancellation: Proper chaining and cleanup via inner_cancellable

This addresses the PR objective of handling concurrent .start() calls and cancellation.

Comment thread ddterm/shell/service.js
Comment on lines +208 to 220
async #wait_subprocess(cancellable) {
this.#subprocess_wait_cancel = new Gio.Cancellable();

this.emit('error', ex);
}).finally(() => {
try {
await this.subprocess.wait_check(cancellable);
} catch (ex) {
if (!this.starting && !ex.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED))
this.emit('error', ex);
} finally {
this.#subprocess_running = false;
this.notify('is-running');
});
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all call sites of #wait_subprocess and usage of #subprocess_wait_cancel
echo "=== Calls to #wait_subprocess ==="
rg -n '#wait_subprocess' ddterm/shell/service.js

echo ""
echo "=== Usage of #subprocess_wait_cancel ==="
rg -n '#subprocess_wait_cancel' ddterm/shell/service.js

Repository: ddterm/gnome-shell-extension-ddterm

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Get more context around line 160 (unwatch) and line 212 (wait_check)
echo "=== Context around unwatch (line 160) ==="
sed -n '155,165p' ddterm/shell/service.js

echo ""
echo "=== Context around wait_check (line 208-220) ==="
sed -n '208,220p' ddterm/shell/service.js

Repository: ddterm/gnome-shell-extension-ddterm

Length of output: 880


Fix the subprocess cancellation logic: the created #subprocess_wait_cancel is never passed to wait_check.

The #wait_subprocess method creates a new Gio.Cancellable stored in #subprocess_wait_cancel (line 209) but passes an undefined cancellable parameter to wait_check (line 212). Since all call sites (lines 115 and 261) invoke #wait_subprocess() without arguments, the cancellable parameter is always undefined. Consequently, unwatch() cannot cancel the subprocess wait—it attempts to cancel #subprocess_wait_cancel, which was never passed to wait_check. Either pass #subprocess_wait_cancel to wait_check, or remove the unused cancellable parameter.

🤖 Prompt for AI Agents
In ddterm/shell/service.js around lines 208 to 220, the method creates
this.#subprocess_wait_cancel but calls this.subprocess.wait_check(cancellable)
with the unused parameter, so the cancellable passed is undefined and
unwatch()/cancel cannot stop the wait; update the method to pass the internal
cancellable to wait_check (i.e. call
this.subprocess.wait_check(this.#subprocess_wait_cancel)) and remove the unused
parameter from the method signature and all callers (or alternatively keep the
parameter and assign it to this.#subprocess_wait_cancel before calling
wait_check) so that the cancellable used to start the wait is the same one
cancelled by unwatch().

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