Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ctx): replace gstuff constructible with oncelock #2267

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from

Conversation

borngraced
Copy link
Member

@borngraced borngraced commented Nov 6, 2024

#1309

Replaced Constructible from the gstuff crate with once_cell::sync::OnceCell for managing lazy-initialized values in mm_ctx for better performance and stronger thread safety.

also add a slight improvement to from_ctx

@borngraced borngraced added the in progress Changes will be made from the author label Nov 6, 2024
@borngraced borngraced self-assigned this Nov 6, 2024
use lazy_static::lazy_static;
use libp2p::PeerId;
use mm2_event_stream::{controller::Controller, Event, EventStreamConfiguration};
use mm2_metrics::{MetricsArc, MetricsOps};
use once_cell::sync::OnceCell;
Copy link
Member

Choose a reason for hiding this comment

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

Why not OnceLock from the standard library?

Copy link
Member Author

@borngraced borngraced Nov 7, 2024

Choose a reason for hiding this comment

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

ah thanks!, I promise, I didn't know this is stable already..also I've had OnceCell on my mind for a long time.

mm2src/mm2_core/src/mm_ctx.rs Show resolved Hide resolved
Comment on lines 717 to 731
// Fast path with try_lock
// Get a context of type T, optimizing for the common case where the context is already initialized.
//
// # Performance Optimization
// This implementation uses a "fast path" optimization with `try_lock()` because:
// - In most cases, the context will already be initialized and threads only need to read it
// - `try_lock()` attempts a non-blocking lock acquisition first, failing fast if locked
// - Only falls back to blocking lock if the fast path fails
//
if let Ok(guard) = ctx.try_lock() {
if let Some(ctx) = guard.as_ref() {
if let Ok(typed_ctx) = ctx.clone().downcast::<T>() {
return Ok(typed_ctx);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

How is that fast path-ing?! the logic is essentially the same as just locking right away.
lock shouldn't block if the lock isn't acquired by anybody.

Copy link
Collaborator

Choose a reason for hiding this comment

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

one other thing regarding from_ctx. it's there to support lazy init of some sub-ctxs we have. IDK really how many of these sub-ctx actually benefit/need lazy initialization, if not too many, we should just initialize things early and abandon from_ctx(sub_ctx, init_clousre) in favor of ctx.sub_ctx.get() (assuming we use OnceLock).

Copy link
Member Author

@borngraced borngraced Nov 7, 2024

Choose a reason for hiding this comment

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

How is that fast path-ing?! the logic is essentially the same as just locking right away.
lock shouldn't block if the lock isn't acquired by anybody.

https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.lock
Acquires a mutex, blocking the current thread until it is able to do so.

This function will block the local thread until it is available to acquire the mutex. Upon returning, the thread is the only thread with the lock held. An RAII guard is returned to allow scoped unlock of the lock. When the guard goes out of scope, the mutex will be unlocked.

https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.try_lock
Attempts to acquire this lock.

If the lock could not be acquired at this time, then Err is returned. Otherwise, an RAII guard is returned. The lock will be unlocked when the guard is dropped.

This function does not block.

Copy link
Collaborator

@mariocynicys mariocynicys Nov 7, 2024

Choose a reason for hiding this comment

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

the argument here is how these two calls are placed. placing them consecutively like that doesn't prevent blocking.

try lock + lock right after is just like a single lock.
also,

lock shouldn't block if the lock isn't acquired by anybody.

so both are equivalent

Copy link
Member Author

Choose a reason for hiding this comment

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

lock shouldn't block if the lock isn't acquired by anybody.

You're right, however, the combination of try_lock() followed by lock() has a different purpose

Using try_lock() followed by lock() isn’t the same as just using lock(). With try_lock(), you get a quick, non-blocking attempt to grab the lock. This can boost performance when the lock is usually free. If try_lock() fails, only then does the code fall back to lock() and wait for the lock, so in summary,

try_lock() followed by lock() adds a non-blocking check first

Copy link
Member Author

@borngraced borngraced Nov 8, 2024

Choose a reason for hiding this comment

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

try lock + lock right after is just like a single lock.

I'm not sure how correct this statement is btw. This can only be true if try_lock() fails

Copy link
Collaborator

Choose a reason for hiding this comment

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

This can only be true if try_lock() fails

exactly!
and if try_lock doesn't fail then lock will not block and this completes the proof of equivalence :)

Copy link
Member Author

@borngraced borngraced Nov 8, 2024

Choose a reason for hiding this comment

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

I understand this is because of how I've utilized them(placing them together), which make this if try_lock doesn't fail then lock will not block and this completes the proof of equivalence true but with a previous try_lock we can't/shouldn't assume anything about lock acquisition timing in concurrent code.

@borngraced borngraced changed the title chore(ctx): replace gstuff constructible with once_cell chore(ctx): replace gstuff constructible with oncelock Nov 8, 2024
@borngraced borngraced added under review and removed in progress Changes will be made from the author labels Nov 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants