Skip to content

feat: add instance id and run id#67

Merged
bzp2010 merged 4 commits into
mainfrom
bzp/feat-instance-id
Apr 29, 2026
Merged

feat: add instance id and run id#67
bzp2010 merged 4 commits into
mainfrom
bzp/feat-instance-id

Conversation

@bzp2010
Copy link
Copy Markdown
Collaborator

@bzp2010 bzp2010 commented Apr 29, 2026

Summary by CodeRabbit

  • New Features

    • Application now initializes and tracks run and instance identifiers at startup, persisting instance IDs across restarts when possible to improve observability and debugging.
  • Tests

    • Added unit tests covering reuse of existing IDs, writing and reading new IDs, and fallback behavior when persistence is unavailable.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Adds a new instance subsystem that initializes a run UUID and a persistent (or fallback in-memory) instance UUID. The application startup (run) now calls instance::init() after observability initialization and before loading configuration; initialization failures are propagated as errors.

Changes

Cohort / File(s) Summary
Instance module
src/utils/instance.rs
New module introducing pub fn init() -> Result<()>, pub fn run_id() -> String, and pub fn instance_id() -> String. Uses OnceLock to store RUN_ID and INSTANCE_ID, reads /tmp/aisix_instance_id, writes a generated ID when missing (logs and keeps in-memory ID if write fails), and includes unit tests for reuse, unreadable path, creation, and non-persistence scenarios.
Utils module export
src/utils/mod.rs
Adds pub mod instance; to export the new instance utility.
Application startup
src/lib.rs
Calls instance::init() in run() (after init_observability() and before config load). Propagates errors with context("failed to initialize instance"). Adjusts imports to bring instance into scope; shutdown behavior unchanged.

Sequence Diagram

sequenceDiagram
    participant App as Application (run)
    participant Observ as Observability
    participant Instance as Instance Module (init)
    participant FS as File System (/tmp/aisix_instance_id)
    participant Lock as OnceLock (state)

    App->>Observ: init_observability()
    Observ-->>App: observability ready
    App->>Instance: call instance::init()
    Instance->>Lock: set RUN_ID (generate UUID)
    Lock-->>Instance: RUN_ID stored
    Instance->>FS: read /tmp/aisix_instance_id
    alt file exists and non-empty
        FS-->>Instance: return persisted ID
        Instance->>Lock: set INSTANCE_ID (persisted)
    else file missing or empty
        Instance->>Instance: generate INSTANCE_ID (UUID)
        Instance->>FS: create parent dirs & write ID
        alt write succeeds
            FS-->>Instance: written
            Instance->>Lock: set INSTANCE_ID (persisted)
        else write fails
            FS-->>Instance: error
            Instance->>Instance: log warning (non-persistent)
            Instance->>Lock: set INSTANCE_ID (in-memory)
        end
    end
    Instance-->>App: return Result (ok / error)
    App->>App: continue loading configuration
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error Instance ID written to /tmp with default permissions (0o644), allowing any user to read it. File operations lack symlink protection and proper permission enforcement. Use OpenOptions with mode(0o600) for restrictive permissions, verify files are regular files (not symlinks), and consider storing instance IDs in protected directories instead of world-writable /tmp.
E2e Test Quality Review ⚠️ Warning PR lacks E2E test coverage validating full application startup flow with instance ID initialization integration. Add E2E tests verifying complete initialization: instance::init() called before accessors, integration with run() function, real-world file system scenarios, and replace .expect() with proper error handling.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add instance id and run id' accurately summarizes the main change - introducing two new identifiers (instance ID and run ID) to the application.
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 bzp/feat-instance-id

Review rate limit: 3/5 reviews remaining, refill in 18 minutes and 54 seconds.

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: 2

🧹 Nitpick comments (4)
src/utils/mod.rs (1)

1-3: Document the new public module.

pub mod instance; exposes a new public surface, but there’s no /// module doc yet. Please add a short summary before exporting it.

As per coding guidelines, Use /// for doc comments on public items in Rust.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/mod.rs` around lines 1 - 3, Add a short module-level documentation
comment for the newly exported public module by placing a /// doc comment
immediately above the pub mod instance; declaration in src/utils/mod.rs; the
comment should be a one-line summary (using /// as required for public items)
describing the purpose of the instance module so the public surface is
documented.
src/utils/instance.rs (3)

22-33: Avoid panicking in the public getters.

run_id() and instance_id() expect() initialization, so any new call site that reaches them before init() will crash the process. Consider returning a borrowed string, Option, or Result instead of hard-expecting initialization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/instance.rs` around lines 22 - 33, The public getters run_id() and
instance_id() currently call .expect(...) on the global RUN_ID and INSTANCE_ID,
which panics if init() hasn't run; change their signatures to return a
non-panicking type (e.g. Option<String> or Result<String, InitError>) that
safely propagates “not initialized” to callers, update the body to map the
get().cloned() into that type instead of .expect(), and update any callers to
handle the Option/Result accordingly; reference the symbols RUN_ID, INSTANCE_ID,
run_id(), instance_id() (and the existing init() that sets them) when making the
changes.

7-7: Don't persist a supposedly stable ID in /tmp.

/tmp/aisix_instance_id can be wiped on reboot or by tmp cleanup, so the instance ID will rotate even when persistence works. If the ID is meant to survive restarts, make the path configurable or move it to a durable data directory.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/instance.rs` at line 7, The constant ID_FILE pointing to
"/tmp/aisix_instance_id" is unsafe for a persistent instance ID; change ID_FILE
from a hardcoded const to a configurable path (read from an env var like
AISIX_INSTANCE_ID_PATH) and fall back to a durable location such as XDG data dir
or /var/lib/<app> (use the directories or dirs crate to compute
XDG_DATA_HOME/aisix and create it if missing). Update functions that read/write
the instance ID (the code using ID_FILE) to accept the resolved path or load it
at startup so the ID survives reboots and tmp cleanup; ensure directory creation
and file permission errors are handled and documented in the config/env fallback
logic.

94-136: Use pretty_assertions::assert_eq in the test assertions.

The equality checks in lines 107, 126, 127, and 131 should use pretty_assertions::assert_eq instead of the standard assert_eq! macro for clearer test failure output. The crate already depends on pretty_assertions = "1.4" and other test files throughout the codebase already follow this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/instance.rs` around lines 94 - 136, Add an import for
pretty_assertions' assert_eq and replace the std macro uses with it: inside the
tests module (mod tests) add "use pretty_assertions::assert_eq;" and change the
equality assertions in reuses_existing_file (the assert_eq comparing
resolve_instance_id_from_path(&path) to "existing-id") and in
writes_new_id_when_file_absent (the assert_eq comparing
std::fs::read_to_string(&path) to id) to use that imported assert_eq function;
leave non-equality asserts (e.g., assert!(!id.is_empty())) as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib.rs`:
- Around line 37-41: The startup logs from instance::init() are lost because
run() initializes observability after calling instance::init(); to fix,
initialize observability first by calling init_observability() (capturing
ob_shutdown_signal/ob_shutdown_task) before invoking instance::init(), or
alternatively silence logging in instance::init(); update run() so the
observability setup (init_observability) occurs prior to instance::init() and
preserve the existing context() error handling around both calls (referencing
run, instance::init, and init_observability to locate the changes).

In `@src/utils/instance.rs`:
- Around line 35-56: resolve_instance_id_from_path currently treats any
read_id_file error as if the file was missing and returns a fresh UUID; change
it to propagate non-NotFound I/O errors instead of collapsing them into a new
id. Specifically, replace the current if-let Ok(Some(id)) / if-let Ok(())
pattern with an explicit match on read_id_file(path) so that match Ok(Some(id))
returns Ok(id), Ok(None) falls through to generate a new UUID and attempt
write_id_file(path, &id) (preserving the info/warn logging behavior), and Err(e)
immediately returns Err(e) from resolve_instance_id_from_path; keep
write_id_file behavior unchanged.

---

Nitpick comments:
In `@src/utils/instance.rs`:
- Around line 22-33: The public getters run_id() and instance_id() currently
call .expect(...) on the global RUN_ID and INSTANCE_ID, which panics if init()
hasn't run; change their signatures to return a non-panicking type (e.g.
Option<String> or Result<String, InitError>) that safely propagates “not
initialized” to callers, update the body to map the get().cloned() into that
type instead of .expect(), and update any callers to handle the Option/Result
accordingly; reference the symbols RUN_ID, INSTANCE_ID, run_id(), instance_id()
(and the existing init() that sets them) when making the changes.
- Line 7: The constant ID_FILE pointing to "/tmp/aisix_instance_id" is unsafe
for a persistent instance ID; change ID_FILE from a hardcoded const to a
configurable path (read from an env var like AISIX_INSTANCE_ID_PATH) and fall
back to a durable location such as XDG data dir or /var/lib/<app> (use the
directories or dirs crate to compute XDG_DATA_HOME/aisix and create it if
missing). Update functions that read/write the instance ID (the code using
ID_FILE) to accept the resolved path or load it at startup so the ID survives
reboots and tmp cleanup; ensure directory creation and file permission errors
are handled and documented in the config/env fallback logic.
- Around line 94-136: Add an import for pretty_assertions' assert_eq and replace
the std macro uses with it: inside the tests module (mod tests) add "use
pretty_assertions::assert_eq;" and change the equality assertions in
reuses_existing_file (the assert_eq comparing
resolve_instance_id_from_path(&path) to "existing-id") and in
writes_new_id_when_file_absent (the assert_eq comparing
std::fs::read_to_string(&path) to id) to use that imported assert_eq function;
leave non-equality asserts (e.g., assert!(!id.is_empty())) as-is.

In `@src/utils/mod.rs`:
- Around line 1-3: Add a short module-level documentation comment for the newly
exported public module by placing a /// doc comment immediately above the pub
mod instance; declaration in src/utils/mod.rs; the comment should be a one-line
summary (using /// as required for public items) describing the purpose of the
instance module so the public surface is documented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8fdc4a0b-56b6-40a4-855c-60040ba32093

📥 Commits

Reviewing files that changed from the base of the PR and between ccfa056 and b509b45.

📒 Files selected for processing (3)
  • src/lib.rs
  • src/utils/instance.rs
  • src/utils/mod.rs

Comment thread src/lib.rs
Comment thread src/utils/instance.rs
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.

🧹 Nitpick comments (2)
src/utils/instance.rs (2)

110-151: Use pretty_assertions::assert_eq! for better test output.

Per coding guidelines, tests should use pretty_assertions::assert_eq for improved diff output on failures.

♻️ Suggested change
 #[cfg(test)]
 mod tests {
     use std::path::PathBuf;
 
+    use pretty_assertions::assert_eq;
+
     use super::*;

As per coding guidelines: "Use pretty_assertions::assert_eq and assert_matches::assert_matches for better test output"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/instance.rs` around lines 110 - 151, Replace the standard
assert_eq! calls in the tests with pretty_assertions::assert_eq! to get better
diff output: update the tests reuses_existing_file and
writes_new_id_when_file_absent to use pretty_assertions::assert_eq! (or add `use
pretty_assertions::assert_eq;` in the test module) while leaving other asserts
as-is; ensure the symbol resolve_instance_id_from_path remains unchanged and
only the assertion macro is swapped.

13-20: Consider early-return if already initialized.

If init() is called a second time, resolve_instance_id_from_path() still performs file I/O and could return an error even though INSTANCE_ID is already populated.

♻️ Suggested improvement
 pub fn init() -> Result<()> {
+    if RUN_ID.get().is_some() && INSTANCE_ID.get().is_some() {
+        return Ok(());
+    }
+
     RUN_ID.get_or_init(|| Uuid::new_v4().to_string());
 
     let instance_id = resolve_instance_id_from_path(Path::new(ID_FILE))?;
     INSTANCE_ID.get_or_init(|| instance_id);
 
     Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/instance.rs` around lines 13 - 20, The init() function should
early-return if INSTANCE_ID is already set to avoid calling
resolve_instance_id_from_path() (which does file I/O) on subsequent calls; add a
check like INSTANCE_ID.get().is_some() and return Ok(()) before calling
resolve_instance_id_from_path(Path::new(ID_FILE)), keep RUN_ID initialization
via RUN_ID.get_or_init(|| Uuid::new_v4().to_string()) as-is, and only call
INSTANCE_ID.get_or_init(|| instance_id) when INSTANCE_ID is not present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/instance.rs`:
- Around line 110-151: Replace the standard assert_eq! calls in the tests with
pretty_assertions::assert_eq! to get better diff output: update the tests
reuses_existing_file and writes_new_id_when_file_absent to use
pretty_assertions::assert_eq! (or add `use pretty_assertions::assert_eq;` in the
test module) while leaving other asserts as-is; ensure the symbol
resolve_instance_id_from_path remains unchanged and only the assertion macro is
swapped.
- Around line 13-20: The init() function should early-return if INSTANCE_ID is
already set to avoid calling resolve_instance_id_from_path() (which does file
I/O) on subsequent calls; add a check like INSTANCE_ID.get().is_some() and
return Ok(()) before calling resolve_instance_id_from_path(Path::new(ID_FILE)),
keep RUN_ID initialization via RUN_ID.get_or_init(|| Uuid::new_v4().to_string())
as-is, and only call INSTANCE_ID.get_or_init(|| instance_id) when INSTANCE_ID is
not present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7ea31b2e-8a82-4175-ac81-c3442da60747

📥 Commits

Reviewing files that changed from the base of the PR and between 10d648c and f56d4ba.

📒 Files selected for processing (1)
  • src/utils/instance.rs

@bzp2010 bzp2010 merged commit ba4259e into main Apr 29, 2026
7 checks passed
@bzp2010 bzp2010 deleted the bzp/feat-instance-id branch April 29, 2026 04:38
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