Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ A Rust procedural macro library for implementing thread-safe singleton patterns

- **Thread-safe**: Uses `std::sync::OnceLock` for safe concurrent access
- **Flexible return types**: Choose between `&'static Self` or `Arc<Self>`
- **Configurable sleep mechanism**: Automatically sleeps and retries when singleton is not yet initialized
- **Simple API**: Just two methods - `init()` and `global()`
- **Zero runtime overhead**: All safety guarantees are compile-time

Expand Down Expand Up @@ -72,6 +73,24 @@ fn main() {
}
```

### Custom Sleep Duration

```rust
use qsinglton::singleton;

// Custom sleep duration of 1000ms
#[singleton(sleep_ms = 1000)]
struct Settings {
timeout: u64,
}

// Arc with custom sleep duration of 300ms
#[singleton(arc, sleep_ms = 300)]
struct Cache {
data: Vec<String>,
}
```

## API

### `init(instance: Self)`
Expand All @@ -80,7 +99,16 @@ Initializes the singleton with the provided instance. Panics if called more than

### `global() -> &'static Self` or `global() -> &'static Arc<Self>`

Returns a reference to the global singleton instance. Panics if called before `init()`.
Returns a reference to the global singleton instance. If the singleton is not yet initialized, this method will sleep for the configured duration (default 500ms) and retry up to 20 times before panicking.

This sleep mechanism is particularly useful in multi-threaded scenarios where one thread is initializing the singleton while another thread attempts to access it.

## Sleep Configuration

- **Default behavior**: `#[singleton]` - 500ms sleep intervals
- **Custom sleep**: `#[singleton(sleep_ms = 1000)]` - Custom duration in milliseconds
- **Arc with custom sleep**: `#[singleton(arc, sleep_ms = 300)]` - Arc mode with custom sleep
- **Maximum retries**: 20 attempts (configurable in generated code)

## Thread Safety

Expand All @@ -89,6 +117,7 @@ All generated code is thread-safe and uses `std::sync::OnceLock` internally to e
- Only one initialization can succeed
- Multiple threads can safely access the singleton
- No data races or undefined behavior
- Graceful handling of initialization delays through configurable sleep mechanism

## License

Expand Down
56 changes: 56 additions & 0 deletions examples/parameter_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use qsingleton::singleton;

// Test various parameter combinations

// Just sleep_ms parameter
#[singleton(sleep_ms = 250)]
#[derive(Debug)]
struct OnlySleep {
data: i32,
}

// Arc with sleep_ms
#[singleton(arc, sleep_ms = 750)]
#[derive(Debug)]
struct ArcWithSleep {
data: String,
}

// Default parameters (should use 500ms)
#[singleton]
#[derive(Debug)]
struct DefaultParams {
data: bool,
}

// Just arc (should use 500ms default)
#[singleton(arc)]
#[derive(Debug)]
struct JustArc {
data: f64,
}

fn main() {
println!("Testing parameter parsing combinations");

// Initialize all singletons
OnlySleep::init(OnlySleep { data: 1 });
ArcWithSleep::init(ArcWithSleep { data: "test".to_string() });
DefaultParams::init(DefaultParams { data: true });
JustArc::init(JustArc { data: 3.14 });

// Access them
let only_sleep = OnlySleep::global();
println!("OnlySleep: {}", only_sleep.data);

let arc_with_sleep = ArcWithSleep::global();
println!("ArcWithSleep: {}", arc_with_sleep.data);

let default_params = DefaultParams::global();
println!("DefaultParams: {}", default_params.data);

let just_arc = JustArc::global();
println!("JustArc: {}", just_arc.data);

println!("All parameter combinations work correctly!");
}
45 changes: 45 additions & 0 deletions examples/simple_sleep.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use qsingleton::singleton;
use std::thread;
use std::time::Duration;

// Basic singleton with default 500ms sleep
#[singleton]
#[derive(Debug)]
struct Config {
name: String,
version: String,
}

fn main() {
println!("QSingleton Sleep Feature Example");
println!("=================================");

// Spawn a thread that tries to access the singleton before it's initialized
let config_thread = thread::spawn(|| {
println!("Thread: Trying to access Config singleton...");
println!("Thread: This will sleep 500ms intervals until initialization completes");
let config = Config::global();
println!("Thread: Success! Got config: {} v{}", config.name, config.version);
});

// Simulate some initialization work
println!("Main: Doing some initialization work for 1.2 seconds...");
thread::sleep(Duration::from_millis(1200));

// Initialize the singleton
println!("Main: Initializing Config singleton");
Config::init(Config {
name: "MyApp".to_string(),
version: "2.0.0".to_string(),
});

// Wait for the thread to complete
config_thread.join().unwrap();

// Show that subsequent accesses are immediate
println!("Main: Accessing Config again (should be immediate)");
let config = Config::global();
println!("Main: Got config immediately: {} v{}", config.name, config.version);

println!("\nExample completed successfully!");
}
109 changes: 109 additions & 0 deletions examples/sleep_demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use qsingleton::singleton;
use std::thread;
use std::time::{Duration, Instant};

// Basic singleton with default 500ms sleep
#[singleton]
#[derive(Debug)]
struct DefaultSleep {
value: String,
}

// Singleton with custom 1000ms sleep
#[singleton(sleep_ms = 1000)]
#[derive(Debug)]
struct CustomSleep {
value: String,
}

// Arc-based singleton with custom 200ms sleep
#[singleton(arc, sleep_ms = 200)]
#[derive(Debug)]
struct ArcCustomSleep {
value: String,
}

fn main() {
println!("Demonstrating sleep functionality in qsingleton");
println!("==============================================");

// Test 1: Default sleep behavior (500ms)
println!("\n1. Testing default sleep (500ms):");

let handle1 = thread::spawn(|| {
println!("Thread 1: Attempting to access DefaultSleep (should sleep and wait)");
let start = Instant::now();
let instance = DefaultSleep::global();
let elapsed = start.elapsed();
println!("Thread 1: Got instance after {:?}: {}", elapsed, instance.value);
});

// Initialize after a delay to test sleep behavior
thread::sleep(Duration::from_millis(750));
println!("Main: Initializing DefaultSleep after 750ms delay");
DefaultSleep::init(DefaultSleep {
value: "Default sleep singleton initialized!".to_string(),
});

handle1.join().unwrap();

// Test 2: Custom sleep behavior (1000ms)
println!("\n2. Testing custom sleep (1000ms):");

let handle2 = thread::spawn(|| {
println!("Thread 2: Attempting to access CustomSleep (should sleep longer)");
let start = Instant::now();
let instance = CustomSleep::global();
let elapsed = start.elapsed();
println!("Thread 2: Got instance after {:?}: {}", elapsed, instance.value);
});

// Initialize after a delay to test custom sleep behavior
thread::sleep(Duration::from_millis(1500));
println!("Main: Initializing CustomSleep after 1500ms delay");
CustomSleep::init(CustomSleep {
value: "Custom sleep singleton initialized!".to_string(),
});

handle2.join().unwrap();

// Test 3: Arc with custom sleep (200ms)
println!("\n3. Testing Arc with custom sleep (200ms):");

let handle3 = thread::spawn(|| {
println!("Thread 3: Attempting to access ArcCustomSleep (fast sleep)");
let start = Instant::now();
let instance = ArcCustomSleep::global();
let elapsed = start.elapsed();
println!("Thread 3: Got Arc instance after {:?}: {}", elapsed, instance.value);
});

// Initialize after a short delay
thread::sleep(Duration::from_millis(300));
println!("Main: Initializing ArcCustomSleep after 300ms delay");
ArcCustomSleep::init(ArcCustomSleep {
value: "Arc custom sleep singleton initialized!".to_string(),
});

handle3.join().unwrap();

// Test 4: Multiple threads accessing the same initialized singleton
println!("\n4. Testing multiple threads accessing initialized singleton:");

let handles: Vec<_> = (0..3)
.map(|i| {
thread::spawn(move || {
let start = Instant::now();
let instance = DefaultSleep::global();
let elapsed = start.elapsed();
println!("Thread {}: Got instance immediately after {:?}: {}", i, elapsed, instance.value);
})
})
.collect();

for handle in handles {
handle.join().unwrap();
}

println!("\nAll tests completed successfully!");
}
48 changes: 48 additions & 0 deletions examples/timeout_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use qsingleton::singleton;
use std::panic;
use std::time::Instant;

#[singleton(sleep_ms = 100)]
#[derive(Debug)]
struct TimeoutTest {
data: String,
}

fn main() {
println!("Testing timeout behavior...");

let result = panic::catch_unwind(|| {
let start = Instant::now();
let _ = TimeoutTest::global(); // This should timeout and panic
start.elapsed()
});

match result {
Ok(_) => {
println!("ERROR: Expected timeout but got success!");
std::process::exit(1);
}
Err(panic_info) => {
if let Some(message) = panic_info.downcast_ref::<String>() {
println!("SUCCESS: Got expected panic message:");
println!("{}", message);

// Verify the message contains expected information
if message.contains("TimeoutTest") &&
message.contains("not initialized") &&
message.contains("20 retries") &&
message.contains("100ms") {
println!("✓ Panic message contains all expected information");
} else {
println!("✗ Panic message missing expected information");
std::process::exit(1);
}
} else {
println!("ERROR: Panic payload is not a string");
std::process::exit(1);
}
}
}

println!("Timeout test completed successfully!");
}
Loading