identity.rs 8.06 KB
Newer Older
Ryan Olson's avatar
Ryan Olson committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Identity types for the active message system.
//!
//! This module provides strongly-typed wrappers for instance and worker identifiers:
//! - [`InstanceId`]: Unique runtime instance identifier (wraps UUID)
//! - [`WorkerId`]: Deterministic 64-bit worker identifier derived from InstanceId
//!
//! # Design Principles
//!
//! 1. **Type Safety**: InstanceId cannot be confused with message IDs or other UUIDs
//! 2. **Deterministic Derivation**: WorkerId is always computed from InstanceId (xxh3_64 hash)
//! 3. **Single Source of Truth**: InstanceId is the primary identifier, WorkerId is derived

use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
use xxhash_rust::xxh3::xxh3_64;

/// Unique identifier for a runtime instance.
///
/// This is a UUID-based identifier that uniquely identifies a running instance
/// of the active message runtime. It is used for:
/// - Transport-level addressing
/// - Discovery registration
/// - Routing table management
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct InstanceId(Uuid);

impl InstanceId {
    /// Create a new random v4 InstanceId.
    ///
    /// This is exposed for testing and special cases. In production, use
    /// [`InstanceFactory::create()`] instead.
    pub fn new_v4() -> Self {
        loop {
            let instance_id = InstanceId(Uuid::new_v4());
            let worker_id = WorkerId::from(&instance_id);
            if worker_id.as_u64() != 0 {
                return instance_id;
            }
        }
    }

    /// Derive the deterministic WorkerId from this InstanceId.
    ///
    /// WorkerId is computed using xxh3_64 hash of the UUID bytes.
    /// This ensures a 1:1 mapping between InstanceId and WorkerId.
    pub fn worker_id(&self) -> WorkerId {
        WorkerId::from(self)
    }

    /// Get a reference to the underlying UUID.
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }

    /// Get the underlying UUID as a u128.
    pub fn as_u128(&self) -> u128 {
        self.0.as_u128()
    }

    /// Get the underlying UUID as bytes.
    pub fn as_bytes(&self) -> &[u8; 16] {
        self.0.as_bytes()
    }
}

impl fmt::Display for InstanceId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Uuid> for InstanceId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<InstanceId> for Uuid {
    fn from(id: InstanceId) -> Self {
        id.0
    }
}

impl AsRef<Uuid> for InstanceId {
    fn as_ref(&self) -> &Uuid {
        &self.0
    }
}

/// Deterministic 64-bit worker identifier derived from InstanceId.
///
/// WorkerId enables embedding instance identity into fixed-size handles that can be
/// passed with value semantics. A `u128` is the largest integer that can be passed
/// by value, making it ideal for handles that encode both routing and event information.
///
/// WorkerId is used in:
/// - `EventHandle` (velo): Uses 64 bits for WorkerId + 64 bits for event details
/// - `EventRoutingTable` (velo): Maps worker_id → instance_id for event routing
/// - Discovery systems: Lookup key for peer information
///
/// WorkerId is **always derived** from InstanceId using xxh3_64 hash.
/// This ensures consistency across the system without needing to store both values.
///
/// # Example
///
/// ```ignore
/// use velo_common::{InstanceId, WorkerId};
///
/// # fn get_instance_id() -> InstanceId { unimplemented!() }
/// let instance_id = get_instance_id(); // From ActiveMessageClient
/// let worker_id = instance_id.worker_id();
///
/// // WorkerId is deterministic
/// assert_eq!(worker_id, instance_id.worker_id());
/// ```
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(transparent)]
pub struct WorkerId(u64);

impl WorkerId {
    /// Create a WorkerId from a raw u64 value.
    ///
    /// This is used when decoding WorkerIds from event handles or wire formats.
    /// External users should always derive WorkerId via `instance_id.worker_id()`.
    pub fn from_u64(value: u64) -> Self {
        Self(value)
    }

    /// Get the underlying u64 value.
    #[inline(always)]
    pub fn as_u64(&self) -> u64 {
        self.0
    }
}

impl fmt::Display for WorkerId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<&InstanceId> for WorkerId {
    /// Derive WorkerId from InstanceId using xxh3_64 hash.
    ///
    /// This is the canonical way to compute WorkerId - it should never be
    /// constructed any other way to ensure consistency.
    fn from(id: &InstanceId) -> Self {
        Self(xxh3_64(id.as_uuid().as_bytes()))
    }
}

impl From<InstanceId> for WorkerId {
    fn from(id: InstanceId) -> Self {
        Self::from(&id)
    }
}

impl From<WorkerId> for u64 {
    fn from(id: WorkerId) -> Self {
        id.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_instance_id_creation() {
        let id1 = InstanceId::new_v4();
        let id2 = InstanceId::new_v4();

        // Different instances have different IDs
        assert_ne!(id1, id2);

        // Can convert to/from UUID
        let uuid: Uuid = id1.into();
        let id3 = InstanceId::from(uuid);
        assert_eq!(id1, id3);
    }

    #[test]
    fn test_worker_id_deterministic() {
        let instance_id = InstanceId::new_v4();

        // WorkerId is deterministic
        let worker_id1 = instance_id.worker_id();
        let worker_id2 = instance_id.worker_id();
        assert_eq!(worker_id1, worker_id2);

        // Different instances have different worker IDs
        let other_instance = InstanceId::new_v4();
        let other_worker = other_instance.worker_id();
        assert_ne!(worker_id1, other_worker);
    }

    #[test]
    fn test_worker_id_from_conversion() {
        let instance_id = InstanceId::new_v4();

        // Both From implementations work
        let worker_id1 = WorkerId::from(&instance_id);
        let worker_id2 = WorkerId::from(instance_id);
        assert_eq!(worker_id1, worker_id2);

        // Matches .worker_id() method
        assert_eq!(worker_id1, instance_id.worker_id());
    }

    #[test]
    fn test_instance_id_display() {
        let instance_id = InstanceId::new_v4();
        let display = format!("{}", instance_id);
        let uuid_display = format!("{}", instance_id.as_uuid());
        assert_eq!(display, uuid_display);
    }

    #[test]
    fn test_worker_id_display() {
        let instance_id = InstanceId::new_v4();
        let worker_id = instance_id.worker_id();
        let display = format!("{}", worker_id);
        let u64_display = format!("{}", worker_id.as_u64());
        assert_eq!(display, u64_display);
    }

    #[test]
    fn test_instance_id_serde() {
        let instance_id = InstanceId::new_v4();

        // Serialize as JSON
        let json = serde_json::to_string(&instance_id).unwrap();

        // Should be a plain UUID string
        let uuid_json = serde_json::to_string(instance_id.as_uuid()).unwrap();
        assert_eq!(json, uuid_json);

        // Deserialize back
        let deserialized: InstanceId = serde_json::from_str(&json).unwrap();
        assert_eq!(instance_id, deserialized);
    }

    #[test]
    fn test_worker_id_serde() {
        let worker_id = InstanceId::new_v4().worker_id();

        // Serialize as JSON
        let json = serde_json::to_string(&worker_id).unwrap();

        // Should be a plain u64
        let u64_json = serde_json::to_string(&worker_id.as_u64()).unwrap();
        assert_eq!(json, u64_json);

        // Deserialize back
        let deserialized: WorkerId = serde_json::from_str(&json).unwrap();
        assert_eq!(worker_id, deserialized);
    }

    #[test]
    fn test_worker_id_u64_conversion() {
        let instance_id = InstanceId::new_v4();
        let worker_id = instance_id.worker_id();

        let raw_u64 = worker_id.as_u64();
        let reconstructed = WorkerId::from_u64(raw_u64);

        assert_eq!(worker_id, reconstructed);
    }
}