12#include <kcenon/common/patterns/result.h>
40 : config_{std::move(other.config_)},
41 locks_{std::move(other.locks_)},
42 token_to_study_{std::move(other.token_to_study_)},
43 stats_{std::move(other.stats_)},
44 next_token_id_{other.next_token_id_.load()},
45 on_lock_acquired_{std::move(
other.on_lock_acquired_)},
46 on_lock_released_{std::move(
other.on_lock_released_)},
47 on_lock_expired_{std::move(
other.on_lock_expired_)} {}
52 std::unique_lock lock{mutex_};
53 config_ = std::move(other.config_);
54 locks_ = std::move(other.locks_);
55 token_to_study_ = std::move(other.token_to_study_);
56 stats_ = std::move(other.stats_);
57 next_token_id_.store(other.next_token_id_.load());
58 on_lock_acquired_ = std::move(other.on_lock_acquired_);
59 on_lock_released_ = std::move(other.on_lock_released_);
60 on_lock_expired_ = std::move(other.on_lock_expired_);
70 const std::string& study_uid,
71 const std::string& reason,
72 const std::string& holder,
78 const std::string& study_uid,
80 const std::string& reason,
81 const std::string& holder,
83 const auto resolved_holder = resolve_holder(holder);
84 const auto expiry = calculate_expiry(timeout);
85 const auto now = std::chrono::system_clock::now();
87 std::unique_lock lock{mutex_};
90 auto it = locks_.find(study_uid);
91 if (it != locks_.end()) {
92 auto& existing = it->second;
95 if (existing.info.is_expired()) {
97 auto token_it = token_to_study_.find(existing.info.token_id);
98 if (token_it != token_to_study_.end()) {
99 token_to_study_.erase(token_it);
105 if (on_lock_expired_) {
107 on_lock_expired_(study_uid, existing.info);
114 it = locks_.find(study_uid);
115 if (it != locks_.end()) {
116 auto& existing = it->second;
122 if (existing.shared_holders.size() >= config_.max_shared_locks) {
124 kcenon::common::error_info{
126 "Maximum shared locks exceeded",
127 "study_lock_manager"});
131 existing.shared_holders.push_back(resolved_holder);
132 existing.info.shared_count = existing.shared_holders.size();
135 const auto token_id = generate_token_id();
136 token_to_study_[token_id] = study_uid;
145 record_acquisition(type);
147 if (on_lock_acquired_) {
149 info.token_id = token_id;
150 info.holder = resolved_holder;
152 on_lock_acquired_(study_uid, info);
160 std::lock_guard stats_lock{stats_mutex_};
161 ++stats_.contention_count;
165 kcenon::common::error_info{
167 "Study is already locked by: " + existing.info.holder,
168 "study_lock_manager",
169 "Lock type: " +
to_string(existing.info.type)});
173 const auto token_id = generate_token_id();
189 locks_[study_uid] = std::move(entry);
190 token_to_study_[token_id] = study_uid;
199 record_acquisition(type);
201 if (on_lock_acquired_) {
204 on_lock_acquired_(study_uid, info);
211 const std::string& study_uid,
213 const std::string& reason,
214 const std::string& holder,
218 return lock(study_uid, type, reason, holder, timeout);
227 std::unique_lock lock{mutex_};
230 auto token_it = token_to_study_.find(token.token_id);
231 if (token_it == token_to_study_.end()) {
233 kcenon::common::error_info{
235 "Invalid or expired token",
236 "study_lock_manager"});
239 const auto study_uid = token_it->second;
240 auto lock_it = locks_.find(study_uid);
241 if (lock_it == locks_.end()) {
243 token_to_study_.erase(token_it);
245 kcenon::common::error_info{
248 "study_lock_manager"});
251 auto& entry = lock_it->second;
252 const auto duration = entry.info.duration();
253 const auto type = entry.info.type;
259 auto holder_it = std::find(
260 entry.shared_holders.begin(),
261 entry.shared_holders.end(),
264 if (holder_it != entry.shared_holders.end()) {
265 entry.shared_holders.erase(holder_it);
267 token_to_study_.erase(token_it);
268 entry.info.shared_count = entry.shared_holders.size();
270 record_release(type, duration);
272 if (on_lock_released_) {
274 on_lock_released_(study_uid, released_info);
281 token_to_study_.erase(token_it);
282 locks_.erase(lock_it);
284 record_release(type, duration);
286 if (on_lock_released_) {
288 on_lock_released_(study_uid, released_info);
295 const std::string& study_uid,
296 const std::string& holder)
298 const auto resolved_holder = resolve_holder(holder);
300 std::unique_lock lock{mutex_};
302 auto lock_it = locks_.find(study_uid);
303 if (lock_it == locks_.end()) {
305 kcenon::common::error_info{
307 "Lock not found for study",
308 "study_lock_manager"});
311 auto& entry = lock_it->second;
314 if (entry.info.holder != resolved_holder) {
316 bool found_shared =
false;
318 auto holder_it = std::find(
319 entry.shared_holders.begin(),
320 entry.shared_holders.end(),
322 if (holder_it != entry.shared_holders.end()) {
324 entry.shared_holders.erase(holder_it);
325 entry.info.shared_count = entry.shared_holders.size();
331 kcenon::common::error_info{
333 "Lock held by different holder: " + entry.info.holder,
334 "study_lock_manager"});
337 if (!entry.shared_holders.empty()) {
339 record_release(entry.info.type, entry.info.duration());
344 const auto duration = entry.info.duration();
345 const auto type = entry.info.type;
349 auto token_it = token_to_study_.find(entry.info.token_id);
350 if (token_it != token_to_study_.end()) {
351 token_to_study_.erase(token_it);
354 locks_.erase(lock_it);
356 record_release(type, duration);
358 if (on_lock_released_) {
360 on_lock_released_(study_uid, released_info);
367 const std::string& study_uid,
368 [[maybe_unused]]
const std::string& admin_reason)
371 if (!config_.allow_force_unlock) {
373 kcenon::common::error_info{
375 "Force unlock is not allowed",
376 "study_lock_manager"});
379 std::unique_lock lock{mutex_};
381 auto lock_it = locks_.find(study_uid);
382 if (lock_it == locks_.end()) {
384 kcenon::common::error_info{
386 "Lock not found for study",
387 "study_lock_manager"});
390 const auto duration = lock_it->second.info.duration();
391 const auto type = lock_it->second.info.type;
392 lock_info released_info = lock_it->second.info;
395 auto token_it = token_to_study_.find(lock_it->second.info.token_id);
396 if (token_it != token_to_study_.end()) {
397 token_to_study_.erase(token_it);
401 for (
const auto& shared_holder : lock_it->second.shared_holders) {
406 locks_.erase(lock_it);
409 std::lock_guard stats_lock{stats_mutex_};
410 ++stats_.force_unlock_count;
412 record_release(type, duration);
414 if (on_lock_released_) {
416 on_lock_released_(study_uid, released_info);
424 const auto resolved_holder = resolve_holder(holder);
425 std::size_t count = 0;
427 std::unique_lock lock{mutex_};
429 std::vector<std::string> to_remove;
430 for (
auto& [study_uid, entry] : locks_) {
431 if (entry.info.holder == resolved_holder) {
432 to_remove.push_back(study_uid);
436 entry.shared_holders.begin(),
437 entry.shared_holders.end(),
439 if (it != entry.shared_holders.end()) {
440 entry.shared_holders.erase(it);
441 entry.info.shared_count = entry.shared_holders.size();
444 if (entry.shared_holders.empty()) {
445 to_remove.push_back(study_uid);
451 for (
const auto& study_uid : to_remove) {
452 auto it = locks_.find(study_uid);
453 if (it != locks_.end()) {
455 auto token_it = token_to_study_.find(it->second.info.token_id);
456 if (token_it != token_to_study_.end()) {
457 token_to_study_.erase(token_it);
460 record_release(it->second.info.type, it->second.info.duration());
474 std::shared_lock lock{mutex_};
476 auto it = locks_.find(study_uid);
477 if (it == locks_.end()) {
482 if (it->second.info.is_expired()) {
490 const std::string& study_uid,
492 std::shared_lock lock{mutex_};
494 auto it = locks_.find(study_uid);
495 if (it == locks_.end()) {
499 if (it->second.info.is_expired()) {
503 return it->second.info.type == type;
507 -> std::optional<lock_info> {
508 std::shared_lock lock{mutex_};
510 auto it = locks_.find(study_uid);
511 if (it == locks_.end()) {
515 if (it->second.info.is_expired()) {
519 return it->second.info;
523 const std::string& token_id)
const
524 -> std::optional<lock_info> {
525 std::shared_lock lock{mutex_};
527 auto token_it = token_to_study_.find(token_id);
528 if (token_it == token_to_study_.end()) {
532 auto lock_it = locks_.find(token_it->second);
533 if (lock_it == locks_.end()) {
537 if (lock_it->second.info.is_expired()) {
541 return lock_it->second.info;
545 std::shared_lock lock{mutex_};
547 auto token_it = token_to_study_.find(token.token_id);
548 if (token_it == token_to_study_.end()) {
552 auto lock_it = locks_.find(token_it->second);
553 if (lock_it == locks_.end()) {
557 if (lock_it->second.info.is_expired()) {
561 return token_it->second == token.study_uid;
566 std::chrono::seconds extension)
568 std::unique_lock lock{mutex_};
570 auto token_it = token_to_study_.find(token.token_id);
571 if (token_it == token_to_study_.end()) {
573 kcenon::common::error_info{
575 "Invalid or expired token",
576 "study_lock_manager"});
579 auto lock_it = locks_.find(token_it->second);
580 if (lock_it == locks_.end()) {
582 kcenon::common::error_info{
585 "study_lock_manager"});
588 if (lock_it->second.info.is_expired()) {
590 kcenon::common::error_info{
593 "study_lock_manager"});
597 auto new_extension = extension.count() > 0 ? extension : config_.default_timeout;
598 if (new_extension.count() > 0) {
599 lock_it->second.info.expires_at =
600 std::chrono::system_clock::now() + new_extension;
604 updated_token.
expires_at = lock_it->second.info.expires_at;
616 std::vector<lock_info> result;
617 result.reserve(
locks_.size());
619 for (
const auto& [study_uid, entry] :
locks_) {
620 if (!entry.info.is_expired()) {
621 result.push_back(entry.info);
629 -> std::vector<lock_info> {
630 const auto resolved_holder = resolve_holder(holder);
631 std::shared_lock lock{mutex_};
633 std::vector<lock_info> result;
635 for (
const auto& [study_uid, entry] : locks_) {
636 if (entry.info.is_expired())
continue;
638 if (entry.info.holder == resolved_holder) {
639 result.push_back(entry.info);
642 entry.shared_holders.begin(),
643 entry.shared_holders.end(),
645 if (it != entry.shared_holders.end()) {
646 result.push_back(entry.info);
655 -> std::vector<lock_info> {
656 std::shared_lock lock{mutex_};
658 std::vector<lock_info> result;
660 for (
const auto& [study_uid, entry] : locks_) {
661 if (!entry.info.is_expired() && entry.info.type == type) {
662 result.push_back(entry.info);
672 std::vector<lock_info> result;
674 for (
const auto& [study_uid, entry] :
locks_) {
675 if (entry.info.is_expired()) {
676 result.push_back(entry.info);
688 std::unique_lock lock{mutex_};
690 std::vector<std::string> expired_studies;
692 for (
const auto& [study_uid, entry] : locks_) {
693 if (entry.info.is_expired()) {
694 expired_studies.push_back(study_uid);
698 for (
const auto& study_uid : expired_studies) {
699 auto it = locks_.find(study_uid);
700 if (it != locks_.end()) {
701 lock_info expired_info = it->second.info;
704 auto token_it = token_to_study_.find(it->second.info.token_id);
705 if (token_it != token_to_study_.end()) {
706 token_to_study_.erase(token_it);
711 if (on_lock_expired_) {
713 on_lock_expired_(study_uid, expired_info);
719 return expired_studies.size();
732 for (
const auto& [study_uid, entry] :
locks_) {
733 if (!entry.info.is_expired()) {
735 switch (entry.info.type) {
749 return current_stats;
789 const auto now = std::chrono::system_clock::now();
790 const auto time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
791 now.time_since_epoch()).count();
793 std::ostringstream oss;
794 oss <<
"lock_" << std::hex << std::setfill(
'0')
795 << std::setw(12) << time_ms
796 <<
"_" << std::setw(8) << id;
803 if (!holder.empty()) {
808 std::ostringstream oss;
809 oss <<
"thread_" << std::this_thread::get_id();
814 -> std::optional<std::chrono::system_clock::time_point> {
815 auto effective_timeout = timeout.count() > 0 ? timeout : config_.default_timeout;
816 if (effective_timeout.count() <= 0) {
820 return std::chrono::system_clock::now() + effective_timeout;
824 const std::string& study_uid,
826 auto it = locks_.find(study_uid);
827 if (it == locks_.end()) {
831 if (it->second.info.is_expired()) {
838 return it->second.shared_holders.size() < config_.max_shared_locks;
852 std::chrono::milliseconds duration) {
858 auto total_duration =
Manages locks on DICOM studies for concurrent access control.
auto calculate_expiry(std::chrono::seconds timeout) const -> std::optional< std::chrono::system_clock::time_point >
Calculate expiration time.
auto is_locked(const std::string &study_uid) const -> bool
Check if a study is locked.
void set_on_lock_expired(lock_event_callback callback)
Set callback for lock expiration events.
std::function< void( const std::string &study_uid, const lock_info &info)> lock_event_callback
Callback type for lock events.
auto force_unlock(const std::string &study_uid, const std::string &admin_reason="") -> kcenon::common::Result< std::monostate >
Force release a lock (admin operation)
study_lock_manager()
Construct lock manager with default configuration.
std::map< std::string, lock_entry > locks_
Lock entries (study_uid -> lock_entry)
auto get_locks_by_type(lock_type type) const -> std::vector< lock_info >
Get all locks of a specific type.
lock_event_callback on_lock_released_
auto get_lock_info(const std::string &study_uid) const -> std::optional< lock_info >
Get lock information for a study.
study_lock_manager & operator=(const study_lock_manager &)=delete
void set_on_lock_released(lock_event_callback callback)
Set callback for lock release events.
auto get_stats() const -> lock_manager_stats
Get lock manager statistics.
void reset_stats()
Reset statistics counters.
auto unlock_all_by_holder(const std::string &holder) -> std::size_t
Release all locks held by a specific holder.
auto get_config() const -> const study_lock_manager_config &
Get the current configuration.
lock_event_callback on_lock_acquired_
Event callbacks.
auto lock(const std::string &study_uid, const std::string &reason, const std::string &holder="", std::chrono::seconds timeout=std::chrono::seconds{0}) -> kcenon::common::Result< lock_token >
Acquire an exclusive lock on a study.
auto resolve_holder(const std::string &holder) const -> std::string
Get holder identifier (uses thread ID if empty)
void set_on_lock_acquired(lock_event_callback callback)
Set callback for lock acquisition events.
auto get_all_locks() const -> std::vector< lock_info >
Get all currently held locks.
auto unlock(const lock_token &token) -> kcenon::common::Result< std::monostate >
Release a lock using its token.
void record_acquisition(lock_type type)
Update statistics on lock acquisition.
auto get_locks_by_holder(const std::string &holder) const -> std::vector< lock_info >
Get all locks held by a specific holder.
~study_lock_manager()
Destructor - releases all locks.
auto get_lock_info_by_token(const std::string &token_id) const -> std::optional< lock_info >
Get lock information by token ID.
auto generate_token_id() const -> std::string
Generate a unique token ID.
std::map< std::string, std::string > token_to_study_
Token to study UID mapping for fast lookup.
auto try_lock(const std::string &study_uid, lock_type type, const std::string &reason, const std::string &holder="", std::chrono::seconds timeout=std::chrono::seconds{0}) -> kcenon::common::Result< lock_token >
Try to acquire a lock without blocking.
std::mutex stats_mutex_
Mutex for statistics.
std::atomic< uint64_t > next_token_id_
Next token ID counter.
auto validate_token(const lock_token &token) const -> bool
Validate a lock token.
auto can_acquire_lock(const std::string &study_uid, lock_type type) const -> bool
Check if a lock can be acquired.
std::shared_mutex mutex_
Mutex for thread-safe access.
lock_event_callback on_lock_expired_
auto get_expired_locks() const -> std::vector< lock_info >
Get all expired locks.
study_lock_manager_config config_
Configuration.
auto refresh_lock(const lock_token &token, std::chrono::seconds extension=std::chrono::seconds{0}) -> kcenon::common::Result< lock_token >
Refresh a lock (extend its timeout)
auto cleanup_expired_locks() -> std::size_t
Clean up expired locks.
void record_release(lock_type type, std::chrono::milliseconds duration)
Update statistics on lock release.
lock_manager_stats stats_
Statistics.
void set_config(const study_lock_manager_config &config)
Update configuration.
@ other
Unknown or other category.
constexpr int max_shared_exceeded
Maximum shared locks exceeded.
constexpr int permission_denied
Permission denied (force unlock not allowed)
constexpr int expired
Lock has expired.
constexpr int already_locked
Lock already held by another holder.
constexpr int invalid_token
Invalid token.
constexpr int not_found
Lock not found.
lock_type
Type of lock to acquire on a study.
@ shared
Read-only access allowed (for read operations)
@ exclusive
No other access allowed (for modifications)
@ migration
Special lock for migration operations (highest priority)
auto to_string(lock_type type) -> std::string
Convert lock_type to string.
Detailed information about a lock on a study.
lock_type type
Type of lock held.
std::size_t shared_count
Number of shared lock holders (for shared locks)
std::string token_id
Lock token ID.
std::string reason
Reason for the lock.
std::chrono::system_clock::time_point acquired_at
When the lock was acquired.
std::string holder
Who holds the lock (user/service identifier)
std::string study_uid
Study UID that is locked.
std::optional< std::chrono::system_clock::time_point > expires_at
When the lock expires (if timeout set)
Statistics for lock manager operations.
std::chrono::milliseconds avg_lock_duration
Average lock duration.
std::size_t total_acquisitions
Total locks acquired.
std::chrono::milliseconds max_lock_duration
Maximum lock duration observed.
std::size_t active_locks
Number of currently held locks.
std::size_t shared_locks
Number of shared locks.
std::size_t exclusive_locks
Number of exclusive locks.
std::size_t migration_locks
Number of migration locks.
std::size_t total_releases
Total locks released.
Unique identifier for a lock.
std::string token_id
Unique token ID.
std::string study_uid
Study UID that is locked.
std::optional< std::chrono::system_clock::time_point > expires_at
When the lock expires (if timeout set)
std::chrono::system_clock::time_point acquired_at
When the lock was acquired.
lock_type type
Type of lock held.
std::vector< std::string > shared_holders
Configuration for the study lock manager.
Study lock manager for modification control and concurrent access.