Skip to content

Service logic moved to kernel#554

Merged
KenVanHoeylandt merged 8 commits into
mainfrom
service-logic-move-to-kernel
Jul 7, 2026
Merged

Service logic moved to kernel#554
KenVanHoeylandt merged 8 commits into
mainfrom
service-logic-move-to-kernel

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added a more complete service lifecycle system, including service registration, start/stop control, and state queries.
    • Introduced service-specific filesystem paths for user data and assets, with safer path handling and buffer checks.
  • Bug Fixes

    • Paired Bluetooth device data now loads from the current settings location instead of a fixed directory.
    • GUI-related services now use a consistent stopped-state check when deciding whether to start.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces the C++-based service instance/context implementation with a new C-language kernel service API (service_instance, service_manager, service_manifest, service_paths, paths). ServiceContext becomes a thin non-owning wrapper over a kernel ServiceInstance pointer, ServiceRegistration delegates to service_manager_* functions via extern "C" trampolines, ServicePaths inlines path lookups through the new C API, and Bluetooth device storage now derives paths from the user-data root. New kernel tests cover paths and service lifecycle behavior, and kernel symbols are extended accordingly.

Changes

Area Change
Kernel headers New service_instance.h, service_manager.h, service_paths.h, paths.h; updated service_manifest.h with on_start/on_stop callbacks
Kernel implementations New service_instance.cpp, service_manager.cpp, service_paths.cpp, paths.cpp; extended kernel_symbols.c
Tactility C++ ServiceContext, ServicePaths, Service.h, ServiceManifest.h, ServiceRegistration.cpp/.h rewired to kernel C API; removed ServiceInstance.h/.cpp
Bluetooth BluetoothPairedDevice.cpp uses computed user-data path instead of fixed directory
LVGL Lvgl.cpp uses SERVICE_STATE_STOPPED instead of service::State::Stopped
Tests New ServicePathsTest.cpp and ServiceTest.cpp

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant service_manager
  participant ServiceInstance
  participant ServiceManifest

  Caller->>service_manager: service_manager_start(id)
  service_manager->>ServiceInstance: service_instance_construct(manifest)
  ServiceInstance->>ServiceManifest: create_service(manifest)
  service_manager->>ServiceManifest: on_start(instance, data)
  service_manager->>ServiceInstance: service_instance_set_state(STARTED)
  Caller->>service_manager: service_manager_stop(id)
  service_manager->>ServiceManifest: on_stop(instance, data)
  service_manager->>ServiceInstance: service_instance_destruct(instance)
Loading

Related PRs: None identified.

Suggested labels: kernel, service-api, breaking-change

Suggested reviewers: TactilityProject maintainers familiar with kernel service subsystem

🐰 A kernel hop, a service spun,
From C++ maps to C, undone,
Paths now flow through kernel light,
Instances start, then stop at night,
Tests confirm the burrow's right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: service logic is being moved into the kernel layer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch service-logic-move-to-kernel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
Tactility/Source/bluetooth/BluetoothPairedDevice.cpp (1)

108-117: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant recomputation of the settings path.

getSettingsFilePath() builds a new string on each call, and here it's invoked twice within the same function (Lines 110 and 113). Minor, but worth caching into a local variable to avoid duplicate string concatenation.

Suggested tweak
 std::vector<PairedDevice> loadAll() {
     std::vector<dirent> entries;
-    if (!file::isDirectory(getSettingsFilePath())) {
+    const auto settings_path = getSettingsFilePath();
+    if (!file::isDirectory(settings_path)) {
         return {};
     }
-    file::scandir(getSettingsFilePath(), entries, [](const dirent* entry) -> int {
+    file::scandir(settings_path, entries, [](const dirent* entry) -> int {
Tests/TactilityKernel/Source/ServiceTest.cpp (2)

4-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fragile ad-hoc extern "C" declaration for internal test hook.

service_instance_set_state is declared here by hand rather than via a shared internal/testing header. If the real signature in service_instance.cpp changes, this won't fail to compile with a clear diagnostic tied to the source of truth — it can silently mismatch at link time. Consider exposing this via a small internal testing header included by both the implementation and this test.


166-182: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting cleanup on start failure.

This test verifies state/context after a failed start, but doesn't assert destroy_called at the end (after service_manager_remove), leaving the destroy path on a failed-start instance unverified.

TactilityKernel/source/paths.cpp (1)

18-21: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Prefer bounded copy over strcpy, even though manually guarded.

Both call sites check length before strcpy, so they aren't currently exploitable, but static analysis flags the raw strcpy pattern (CWE-120), and the rest of the file already uses snprintf for bounded writes. Switching to snprintf for consistency removes the lint noise and guards against future edits that might remove the preceding length check.

Suggested fix using snprintf
-    if (std::strlen(mount_point) + 1 > out_path_size) {
-        return ERROR_BUFFER_OVERFLOW;
-    }
-    std::strcpy(out_path, mount_point);
-    return ERROR_NONE;
+    int written = std::snprintf(out_path, out_path_size, "%s", mount_point);
+    if (written < 0 || (size_t)written >= out_path_size) {
+        return ERROR_BUFFER_OVERFLOW;
+    }
+    return ERROR_NONE;

(apply the same change to the second strcpy call site)

Also applies to: 58-61

Source: Linters/SAST tools


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6abd1ca9-84eb-4b18-8748-65caabaf9b69

📥 Commits

Reviewing files that changed from the base of the PR and between cca7224 and 469cf0d.

📒 Files selected for processing (20)
  • Tactility/Include/Tactility/service/Service.h
  • Tactility/Include/Tactility/service/ServicePaths.h
  • Tactility/Private/Tactility/service/ServiceInstance.h
  • Tactility/Source/bluetooth/BluetoothPairedDevice.cpp
  • Tactility/Source/lvgl/Lvgl.cpp
  • Tactility/Source/service/ServiceInstance.cpp
  • Tactility/Source/service/ServicePaths.cpp
  • Tactility/Source/service/ServiceRegistration.cpp
  • TactilityKernel/include/tactility/paths.h
  • TactilityKernel/include/tactility/service/service_context.h
  • TactilityKernel/include/tactility/service/service_instance.h
  • TactilityKernel/include/tactility/service/service_manager.h
  • TactilityKernel/include/tactility/service/service_manifest.h
  • TactilityKernel/include/tactility/service/service_paths.h
  • TactilityKernel/source/paths.cpp
  • TactilityKernel/source/service/service_instance.cpp
  • TactilityKernel/source/service/service_manager.cpp
  • TactilityKernel/source/service/service_paths.cpp
  • Tests/TactilityKernel/Source/ServicePathsTest.cpp
  • Tests/TactilityKernel/Source/ServiceTest.cpp
💤 Files with no reviewable changes (2)
  • Tactility/Source/service/ServicePaths.cpp
  • Tactility/Source/service/ServiceInstance.cpp

Comment thread Tactility/Source/bluetooth/BluetoothPairedDevice.cpp
Comment thread TactilityKernel/source/service/service_instance.cpp
Comment thread TactilityKernel/source/service/service_manager.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
TactilityKernel/source/service/service_manager.cpp (2)

67-83: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

TOCTOU race between remove()'s state check and the manifest erase.

service_manager_get_state(id) locks/unlocks instance_ledger.mutex internally, then manifest_ledger.mutex is acquired separately for the erase. A concurrent service_manager_start(id) can create and register a running instance in that gap, so remove() can erase the manifest for a service that is now actively running.

🔒 Proposed fix: hold `manifest_ledger.mutex` across the whole check-then-erase
 error_t service_manager_remove(const char* id) {
-    if (service_manager_get_state(id) != SERVICE_STATE_STOPPED) {
-        return ERROR_INVALID_STATE;
-    }
-
     mutex_lock(&manifest_ledger.mutex);
+    mutex_lock(&instance_ledger.mutex);
+    const bool running = instance_ledger.instances.contains(id);
+    mutex_unlock(&instance_ledger.mutex);
+    if (running) {
+        mutex_unlock(&manifest_ledger.mutex);
+        return ERROR_INVALID_STATE;
+    }
+
     const auto iterator = manifest_ledger.manifests.find(id);
     if (iterator == manifest_ledger.manifests.end()) {
         mutex_unlock(&manifest_ledger.mutex);
         return ERROR_NOT_FOUND;
     }
     manifest_ledger.manifests.erase(iterator);
     mutex_unlock(&manifest_ledger.mutex);

Since service_manager_start() also locks manifest_ledger first (and never nests it inside instance_ledger), this preserves lock ordering without introducing deadlock risk.


95-116: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Global instance_ledger mutex is held across an external callback (manifest->create_service).

instance_ledger.mutex — a single mutex shared by every service in the system — stays locked from line 95 through line 116, which spans the service_instance_construct() call at line 107. That call invokes manifest->create_service(manifest) directly. Any service whose create_service does non-trivial work, blocks, or (worst case) calls back into any service_manager_* function will stall or deadlock every other service's start/stop/state lookup in the system.

Consider reserving the id under the lock (e.g., inserting a placeholder before construction), releasing the lock before invoking service_instance_construct()/create_service(), and re-acquiring it only to finalize or roll back the reservation. This does require get_state/find_instance to tolerate the reserved-but-not-yet-constructed state.

♻️ Duplicate comments (1)
TactilityKernel/source/service/service_manager.cpp (1)

114-139: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Use-after-free: start() still uses instance after on_start() returns, with no pin against a concurrent stop().

This was already flagged in a previous review pass on this PR ("Use-after-free if stop() runs while on_start() is still executing"). The instance is inserted into instance_ledger and its state set to STARTING (lines 115-118) before manifest->on_start() runs unlocked (line 121). Once on_start() returns, start() dereferences the same instance again (lines 123-136) to set its final state, and on failure to erase/destruct/delete it. In the interim, instance_ledger already exposes this instance to a concurrent service_manager_stop(id), which can itself erase, destruct(), and delete instance once state reaches STOPPING/STOPPED. There is no refcount/pin left in the new API (previous suggestion to gate on use_count no longer applies since that mechanism has been removed entirely), so this race is unresolved and now has zero mitigation.

A fix likely needs a lifetime-pinning mechanism (e.g., re-introduce a use-count/generation check, or hold instance ownership exclusively in the starting thread until on_start() fully completes and only then make it visible/stoppable).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2793ccab-8e3e-4343-815b-f01accb9dda5

📥 Commits

Reviewing files that changed from the base of the PR and between a2a802e and affdd61.

📒 Files selected for processing (15)
  • Tactility/Include/Tactility/service/Service.h
  • Tactility/Include/Tactility/service/ServiceContext.h
  • Tactility/Include/Tactility/service/ServiceManifest.h
  • Tactility/Include/Tactility/service/ServicePaths.h
  • Tactility/Include/Tactility/service/ServiceRegistration.h
  • Tactility/Private/Tactility/service/ServiceInstance.h
  • Tactility/Source/service/ServiceContext.cpp
  • Tactility/Source/service/ServiceRegistration.cpp
  • TactilityKernel/include/tactility/service/service_instance.h
  • TactilityKernel/include/tactility/service/service_manager.h
  • TactilityKernel/include/tactility/service/service_manifest.h
  • TactilityKernel/source/kernel_symbols.c
  • TactilityKernel/source/service/service_instance.cpp
  • TactilityKernel/source/service/service_manager.cpp
  • Tests/TactilityKernel/Source/ServiceTest.cpp
💤 Files with no reviewable changes (1)
  • Tactility/Private/Tactility/service/ServiceInstance.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/TactilityKernel/Source/ServiceTest.cpp
  • Tactility/Source/service/ServiceRegistration.cpp

Comment thread Tactility/Include/Tactility/service/ServiceContext.h
@KenVanHoeylandt KenVanHoeylandt merged commit f4f91f8 into main Jul 7, 2026
55 checks passed
@KenVanHoeylandt KenVanHoeylandt deleted the service-logic-move-to-kernel branch July 7, 2026 20:52
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