PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::workflow::study_lock_manager Class Reference

Manages locks on DICOM studies for concurrent access control. More...

#include <study_lock_manager.h>

Collaboration diagram for kcenon::pacs::workflow::study_lock_manager:
Collaboration graph

Classes

struct  lock_entry
 

Public Types

using lock_event_callback
 Callback type for lock events.
 

Public Member Functions

 study_lock_manager ()
 Construct lock manager with default configuration.
 
 study_lock_manager (const study_lock_manager_config &config)
 Construct lock manager with custom configuration.
 
 ~study_lock_manager ()
 Destructor - releases all locks.
 
 study_lock_manager (const study_lock_manager &)=delete
 Non-copyable.
 
study_lock_manageroperator= (const study_lock_manager &)=delete
 
 study_lock_manager (study_lock_manager &&) noexcept
 Movable.
 
study_lock_manageroperator= (study_lock_manager &&) noexcept
 
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 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 >
 Acquire a lock of specific type on a study.
 
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.
 
auto unlock (const lock_token &token) -> kcenon::common::Result< std::monostate >
 Release a lock using its token.
 
auto unlock (const std::string &study_uid, const std::string &holder) -> kcenon::common::Result< std::monostate >
 Release a lock by study UID and holder.
 
auto force_unlock (const std::string &study_uid, const std::string &admin_reason="") -> kcenon::common::Result< std::monostate >
 Force release a lock (admin operation)
 
auto unlock_all_by_holder (const std::string &holder) -> std::size_t
 Release all locks held by a specific holder.
 
auto is_locked (const std::string &study_uid) const -> bool
 Check if a study is locked.
 
auto is_locked (const std::string &study_uid, lock_type type) const -> bool
 Check if a study has a specific lock type.
 
auto get_lock_info (const std::string &study_uid) const -> std::optional< lock_info >
 Get lock information for a study.
 
auto get_lock_info_by_token (const std::string &token_id) const -> std::optional< lock_info >
 Get lock information by token ID.
 
auto validate_token (const lock_token &token) const -> bool
 Validate a lock token.
 
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 get_all_locks () const -> std::vector< lock_info >
 Get all currently held locks.
 
auto get_locks_by_holder (const std::string &holder) const -> std::vector< lock_info >
 Get all locks held by a specific holder.
 
auto get_locks_by_type (lock_type type) const -> std::vector< lock_info >
 Get all locks of a specific type.
 
auto get_expired_locks () const -> std::vector< lock_info >
 Get all expired locks.
 
auto cleanup_expired_locks () -> std::size_t
 Clean up expired locks.
 
auto get_stats () const -> lock_manager_stats
 Get lock manager statistics.
 
void reset_stats ()
 Reset statistics counters.
 
auto get_config () const -> const study_lock_manager_config &
 Get the current configuration.
 
void set_config (const study_lock_manager_config &config)
 Update configuration.
 
void set_on_lock_acquired (lock_event_callback callback)
 Set callback for lock acquisition events.
 
void set_on_lock_released (lock_event_callback callback)
 Set callback for lock release events.
 
void set_on_lock_expired (lock_event_callback callback)
 Set callback for lock expiration events.
 

Private Member Functions

auto generate_token_id () const -> std::string
 Generate a unique token ID.
 
auto resolve_holder (const std::string &holder) const -> std::string
 Get holder identifier (uses thread ID if empty)
 
auto calculate_expiry (std::chrono::seconds timeout) const -> std::optional< std::chrono::system_clock::time_point >
 Calculate expiration time.
 
auto can_acquire_lock (const std::string &study_uid, lock_type type) const -> bool
 Check if a lock can be acquired.
 
void record_acquisition (lock_type type)
 Update statistics on lock acquisition.
 
void record_release (lock_type type, std::chrono::milliseconds duration)
 Update statistics on lock release.
 

Private Attributes

study_lock_manager_config config_
 Configuration.
 
std::map< std::string, lock_entrylocks_
 Lock entries (study_uid -> lock_entry)
 
std::map< std::string, std::string > token_to_study_
 Token to study UID mapping for fast lookup.
 
std::shared_mutex mutex_
 Mutex for thread-safe access.
 
lock_manager_stats stats_
 Statistics.
 
std::mutex stats_mutex_
 Mutex for statistics.
 
std::atomic< uint64_t > next_token_id_ {1}
 Next token ID counter.
 
lock_event_callback on_lock_acquired_
 Event callbacks.
 
lock_event_callback on_lock_released_
 
lock_event_callback on_lock_expired_
 

Detailed Description

Manages locks on DICOM studies for concurrent access control.

The study_lock_manager provides thread-safe locking mechanisms for DICOM studies to prevent concurrent modifications and ensure data integrity.

Key Features

  • Exclusive Locks: Prevent all other access to a study
  • Shared Locks: Allow concurrent read access
  • Migration Locks: High-priority locks for migration operations
  • Automatic Timeout: Locks can expire after a configured duration
  • Force Unlock: Admin capability to forcibly release locks

Integration with kcenon Ecosystem

  • thread_system: Thread-safe operations via shared_mutex
  • logger_system: Audit trails for lock operations
  • monitoring_system: Lock contention and duration metrics
  • common_system: Result<T> for error handling

Lock Priority

When multiple lock requests compete:

  1. Migration locks have highest priority
  2. Exclusive locks block new shared locks
  3. Shared locks can coexist with other shared locks

Definition at line 337 of file study_lock_manager.h.

Member Typedef Documentation

◆ lock_event_callback

Initial value:
std::function<void(
const std::string& study_uid,
const lock_info& info)>

Callback type for lock events.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 610 of file study_lock_manager.h.

Constructor & Destructor Documentation

◆ study_lock_manager() [1/4]

kcenon::pacs::workflow::study_lock_manager::study_lock_manager ( )

Construct lock manager with default configuration.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 26 of file study_lock_manager.cpp.

27 : config_{} {}
study_lock_manager_config config_
Configuration.

◆ study_lock_manager() [2/4]

kcenon::pacs::workflow::study_lock_manager::study_lock_manager ( const study_lock_manager_config & config)
explicit

Construct lock manager with custom configuration.

Parameters
configLock manager configuration

Definition at line 29 of file study_lock_manager.cpp.

30 : config_{config} {}

◆ ~study_lock_manager()

kcenon::pacs::workflow::study_lock_manager::~study_lock_manager ( )

Destructor - releases all locks.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 32 of file study_lock_manager.cpp.

32 {
33 // Release all locks on destruction
34 std::unique_lock lock{mutex_};
35 locks_.clear();
36 token_to_study_.clear();
37}
std::map< std::string, lock_entry > locks_
Lock entries (study_uid -> lock_entry)
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.
std::map< std::string, std::string > token_to_study_
Token to study UID mapping for fast lookup.
std::shared_mutex mutex_
Mutex for thread-safe access.

References lock(), locks_, mutex_, and token_to_study_.

Here is the call graph for this function:

◆ study_lock_manager() [3/4]

kcenon::pacs::workflow::study_lock_manager::study_lock_manager ( const study_lock_manager & )
delete

Non-copyable.

◆ study_lock_manager() [4/4]

kcenon::pacs::workflow::study_lock_manager::study_lock_manager ( study_lock_manager && other)
noexcept

Movable.

Definition at line 39 of file study_lock_manager.cpp.

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_)} {}
lock_event_callback on_lock_acquired_
Event callbacks.
std::atomic< uint64_t > next_token_id_
Next token ID counter.

Member Function Documentation

◆ calculate_expiry()

auto kcenon::pacs::workflow::study_lock_manager::calculate_expiry ( std::chrono::seconds timeout) const -> std::optional<std::chrono::system_clock::time_point>
nodiscardprivate

Calculate expiration time.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 813 of file study_lock_manager.cpp.

814 {
815 auto effective_timeout = timeout.count() > 0 ? timeout : config_.default_timeout;
816 if (effective_timeout.count() <= 0) {
817 return std::nullopt; // No expiration
818 }
819
820 return std::chrono::system_clock::now() + effective_timeout;
821}
constexpr int timeout
Lock timeout exceeded.
std::chrono::seconds default_timeout
Default lock timeout (0 = no timeout)

◆ can_acquire_lock()

auto kcenon::pacs::workflow::study_lock_manager::can_acquire_lock ( const std::string & study_uid,
lock_type type ) const -> bool
nodiscardprivate

Check if a lock can be acquired.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 823 of file study_lock_manager.cpp.

825 {
826 auto it = locks_.find(study_uid);
827 if (it == locks_.end()) {
828 return true;
829 }
830
831 if (it->second.info.is_expired()) {
832 return true;
833 }
834
835 // Shared locks can coexist with other shared locks
836 if (type == lock_type::shared &&
837 it->second.info.type == lock_type::shared) {
838 return it->second.shared_holders.size() < config_.max_shared_locks;
839 }
840
841 return false;
842}
@ shared
Read-only access allowed (for read operations)
std::size_t max_shared_locks
Maximum number of concurrent shared locks.

References kcenon::pacs::workflow::shared.

◆ cleanup_expired_locks()

auto kcenon::pacs::workflow::study_lock_manager::cleanup_expired_locks ( ) -> std::size_t

Clean up expired locks.

Returns
Number of expired locks cleaned up
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 687 of file study_lock_manager.cpp.

687 {
688 std::unique_lock lock{mutex_};
689
690 std::vector<std::string> expired_studies;
691
692 for (const auto& [study_uid, entry] : locks_) {
693 if (entry.info.is_expired()) {
694 expired_studies.push_back(study_uid);
695 }
696 }
697
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;
702
703 // Remove token mapping
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);
707 }
708
709 locks_.erase(it);
710
711 if (on_lock_expired_) {
712 lock.unlock();
713 on_lock_expired_(study_uid, expired_info);
714 lock.lock();
715 }
716 }
717 }
718
719 return expired_studies.size();
720}

◆ force_unlock()

auto kcenon::pacs::workflow::study_lock_manager::force_unlock ( const std::string & study_uid,
const std::string & admin_reason = "" ) -> kcenon::common::Result<std::monostate>
nodiscard

Force release a lock (admin operation)

Parameters
study_uidStudy UID to unlock
admin_reasonReason for force unlock
Returns
Result indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 366 of file study_lock_manager.cpp.

369 {
370 // Note: admin_reason can be used for audit logging in future
373 kcenon::common::error_info{
375 "Force unlock is not allowed",
376 "study_lock_manager"});
377 }
378
379 std::unique_lock lock{mutex_};
380
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"});
388 }
389
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;
393
394 // Remove token mapping
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);
398 }
399
400 // Remove all shared holder tokens
401 for (const auto& shared_holder : lock_it->second.shared_holders) {
402 // Shared holders may have separate tokens - clean up if needed
403 (void)shared_holder;
404 }
405
406 locks_.erase(lock_it);
407
408 {
409 std::lock_guard stats_lock{stats_mutex_};
411 }
412 record_release(type, duration);
413
414 if (on_lock_released_) {
415 lock.unlock();
416 on_lock_released_(study_uid, released_info);
417 }
418
419 return kcenon::common::Result<std::monostate>::ok(std::monostate{});
420}
std::mutex stats_mutex_
Mutex for statistics.
void record_release(lock_type type, std::chrono::milliseconds duration)
Update statistics on lock release.
constexpr int permission_denied
Permission denied (force unlock not allowed)
constexpr int not_found
Lock not found.
std::size_t force_unlock_count
Locks that were forcibly released.
bool allow_force_unlock
Allow force unlock for admin operations.

References kcenon::pacs::workflow::lock_error::not_found, and kcenon::pacs::workflow::lock_error::permission_denied.

◆ generate_token_id()

auto kcenon::pacs::workflow::study_lock_manager::generate_token_id ( ) const -> std::string
nodiscardprivate

Generate a unique token ID.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 787 of file study_lock_manager.cpp.

787 {
788 const auto id = next_token_id_.fetch_add(1);
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();
792
793 std::ostringstream oss;
794 oss << "lock_" << std::hex << std::setfill('0')
795 << std::setw(12) << time_ms
796 << "_" << std::setw(8) << id;
797
798 return oss.str();
799}
@ id
Implant Displaced (alternate code)

References next_token_id_.

◆ get_all_locks()

auto kcenon::pacs::workflow::study_lock_manager::get_all_locks ( ) const -> std::vector<lock_info>
nodiscard

Get all currently held locks.

Returns
Vector of lock information for all held locks
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 613 of file study_lock_manager.cpp.

613 {
614 std::shared_lock lock{mutex_};
615
616 std::vector<lock_info> result;
617 result.reserve(locks_.size());
618
619 for (const auto& [study_uid, entry] : locks_) {
620 if (!entry.info.is_expired()) {
621 result.push_back(entry.info);
622 }
623 }
624
625 return result;
626}

References lock(), locks_, and mutex_.

Here is the call graph for this function:

◆ get_config()

auto kcenon::pacs::workflow::study_lock_manager::get_config ( ) const -> const study_lock_manager_config&
nodiscard

Get the current configuration.

Returns
Current configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 757 of file study_lock_manager.cpp.

758 {
759 return config_;
760}

References config_.

◆ get_expired_locks()

auto kcenon::pacs::workflow::study_lock_manager::get_expired_locks ( ) const -> std::vector<lock_info>
nodiscard

Get all expired locks.

Returns
Vector of lock information for expired locks
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 669 of file study_lock_manager.cpp.

669 {
670 std::shared_lock lock{mutex_};
671
672 std::vector<lock_info> result;
673
674 for (const auto& [study_uid, entry] : locks_) {
675 if (entry.info.is_expired()) {
676 result.push_back(entry.info);
677 }
678 }
679
680 return result;
681}

References lock(), locks_, and mutex_.

Here is the call graph for this function:

◆ get_lock_info()

auto kcenon::pacs::workflow::study_lock_manager::get_lock_info ( const std::string & study_uid) const -> std::optional<lock_info>
nodiscard

Get lock information for a study.

Parameters
study_uidStudy UID to query
Returns
Lock information if locked, nullopt otherwise
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 506 of file study_lock_manager.cpp.

507 {
508 std::shared_lock lock{mutex_};
509
510 auto it = locks_.find(study_uid);
511 if (it == locks_.end()) {
512 return std::nullopt;
513 }
514
515 if (it->second.info.is_expired()) {
516 return std::nullopt;
517 }
518
519 return it->second.info;
520}

◆ get_lock_info_by_token()

auto kcenon::pacs::workflow::study_lock_manager::get_lock_info_by_token ( const std::string & token_id) const -> std::optional<lock_info>
nodiscard

Get lock information by token ID.

Parameters
token_idToken ID to query
Returns
Lock information if found, nullopt otherwise
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 522 of file study_lock_manager.cpp.

524 {
525 std::shared_lock lock{mutex_};
526
527 auto token_it = token_to_study_.find(token_id);
528 if (token_it == token_to_study_.end()) {
529 return std::nullopt;
530 }
531
532 auto lock_it = locks_.find(token_it->second);
533 if (lock_it == locks_.end()) {
534 return std::nullopt;
535 }
536
537 if (lock_it->second.info.is_expired()) {
538 return std::nullopt;
539 }
540
541 return lock_it->second.info;
542}

◆ get_locks_by_holder()

auto kcenon::pacs::workflow::study_lock_manager::get_locks_by_holder ( const std::string & holder) const -> std::vector<lock_info>
nodiscard

Get all locks held by a specific holder.

Parameters
holderIdentifier of the lock holder
Returns
Vector of lock information for the holder's locks
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 628 of file study_lock_manager.cpp.

629 {
630 const auto resolved_holder = resolve_holder(holder);
631 std::shared_lock lock{mutex_};
632
633 std::vector<lock_info> result;
634
635 for (const auto& [study_uid, entry] : locks_) {
636 if (entry.info.is_expired()) continue;
637
638 if (entry.info.holder == resolved_holder) {
639 result.push_back(entry.info);
640 } else if (entry.info.type == lock_type::shared) {
641 auto it = std::find(
642 entry.shared_holders.begin(),
643 entry.shared_holders.end(),
644 resolved_holder);
645 if (it != entry.shared_holders.end()) {
646 result.push_back(entry.info);
647 }
648 }
649 }
650
651 return result;
652}
auto resolve_holder(const std::string &holder) const -> std::string
Get holder identifier (uses thread ID if empty)

References kcenon::pacs::workflow::shared.

◆ get_locks_by_type()

auto kcenon::pacs::workflow::study_lock_manager::get_locks_by_type ( lock_type type) const -> std::vector<lock_info>
nodiscard

Get all locks of a specific type.

Parameters
typeLock type to filter by
Returns
Vector of lock information for matching locks
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 654 of file study_lock_manager.cpp.

655 {
656 std::shared_lock lock{mutex_};
657
658 std::vector<lock_info> result;
659
660 for (const auto& [study_uid, entry] : locks_) {
661 if (!entry.info.is_expired() && entry.info.type == type) {
662 result.push_back(entry.info);
663 }
664 }
665
666 return result;
667}

◆ get_stats()

auto kcenon::pacs::workflow::study_lock_manager::get_stats ( ) const -> lock_manager_stats
nodiscard

Get lock manager statistics.

Returns
Current statistics
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 722 of file study_lock_manager.cpp.

722 {
723 std::lock_guard stats_lock{stats_mutex_};
724 std::shared_lock lock{mutex_};
725
726 lock_manager_stats current_stats = stats_;
727 current_stats.active_locks = 0;
728 current_stats.exclusive_locks = 0;
729 current_stats.shared_locks = 0;
730 current_stats.migration_locks = 0;
731
732 for (const auto& [study_uid, entry] : locks_) {
733 if (!entry.info.is_expired()) {
734 ++current_stats.active_locks;
735 switch (entry.info.type) {
737 ++current_stats.exclusive_locks;
738 break;
740 ++current_stats.shared_locks;
741 break;
743 ++current_stats.migration_locks;
744 break;
745 }
746 }
747 }
748
749 return current_stats;
750}
@ exclusive
No other access allowed (for modifications)
@ migration
Special lock for migration operations (highest priority)
std::size_t active_locks
Number of currently held locks.

References kcenon::pacs::workflow::lock_manager_stats::active_locks, kcenon::pacs::workflow::exclusive, kcenon::pacs::workflow::lock_manager_stats::exclusive_locks, lock(), locks_, kcenon::pacs::workflow::migration, kcenon::pacs::workflow::lock_manager_stats::migration_locks, mutex_, kcenon::pacs::workflow::shared, kcenon::pacs::workflow::lock_manager_stats::shared_locks, stats_, and stats_mutex_.

Here is the call graph for this function:

◆ is_locked() [1/2]

auto kcenon::pacs::workflow::study_lock_manager::is_locked ( const std::string & study_uid) const -> bool
nodiscard

Check if a study is locked.

Parameters
study_uidStudy UID to check
Returns
true if the study is locked
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 473 of file study_lock_manager.cpp.

473 {
474 std::shared_lock lock{mutex_};
475
476 auto it = locks_.find(study_uid);
477 if (it == locks_.end()) {
478 return false;
479 }
480
481 // Check if expired
482 if (it->second.info.is_expired()) {
483 return false;
484 }
485
486 return true;
487}

◆ is_locked() [2/2]

auto kcenon::pacs::workflow::study_lock_manager::is_locked ( const std::string & study_uid,
lock_type type ) const -> bool
nodiscard

Check if a study has a specific lock type.

Parameters
study_uidStudy UID to check
typeLock type to check for
Returns
true if the study has the specified lock type

Definition at line 489 of file study_lock_manager.cpp.

491 {
492 std::shared_lock lock{mutex_};
493
494 auto it = locks_.find(study_uid);
495 if (it == locks_.end()) {
496 return false;
497 }
498
499 if (it->second.info.is_expired()) {
500 return false;
501 }
502
503 return it->second.info.type == type;
504}

◆ lock() [1/2]

auto kcenon::pacs::workflow::study_lock_manager::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>
nodiscard

Acquire an exclusive lock on a study.

Parameters
study_uidStudy UID to lock
reasonReason for acquiring the lock
holderIdentifier of the lock holder (default: current thread)
timeoutOptional timeout for the lock (0 = use default)
Returns
Result containing lock_token or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 69 of file study_lock_manager.cpp.

73 {
74 return lock(study_uid, lock_type::exclusive, reason, holder, timeout);
75}

References kcenon::pacs::workflow::exclusive.

Referenced by get_all_locks(), get_expired_locks(), get_stats(), set_config(), and ~study_lock_manager().

Here is the caller graph for this function:

◆ lock() [2/2]

auto kcenon::pacs::workflow::study_lock_manager::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>
nodiscard

Acquire a lock of specific type on a study.

Parameters
study_uidStudy UID to lock
typeType of lock to acquire
reasonReason for acquiring the lock
holderIdentifier of the lock holder
timeoutOptional timeout for the lock
Returns
Result containing lock_token or error

Definition at line 77 of file study_lock_manager.cpp.

82 {
83 const auto resolved_holder = resolve_holder(holder);
84 const auto expiry = calculate_expiry(timeout);
85 const auto now = std::chrono::system_clock::now();
86
87 std::unique_lock lock{mutex_};
88
89 // Check if lock can be acquired
90 auto it = locks_.find(study_uid);
91 if (it != locks_.end()) {
92 auto& existing = it->second;
93
94 // Check if existing lock is expired
95 if (existing.info.is_expired()) {
96 // Clean up expired lock
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);
100 }
101 locks_.erase(it);
102 it = locks_.end();
103
104 // Notify expiration
105 if (on_lock_expired_) {
106 lock.unlock();
107 on_lock_expired_(study_uid, existing.info);
108 lock.lock();
109 }
110 }
111 }
112
113 // Re-check after potential cleanup
114 it = locks_.find(study_uid);
115 if (it != locks_.end()) {
116 auto& existing = it->second;
117
118 // Handle shared lock upgrade/coexistence
119 if (type == lock_type::shared &&
120 existing.info.type == lock_type::shared) {
121 // Check max shared locks
122 if (existing.shared_holders.size() >= config_.max_shared_locks) {
124 kcenon::common::error_info{
126 "Maximum shared locks exceeded",
127 "study_lock_manager"});
128 }
129
130 // Add holder to shared list
131 existing.shared_holders.push_back(resolved_holder);
132 existing.info.shared_count = existing.shared_holders.size();
133
134 // Generate new token for this shared holder
135 const auto token_id = generate_token_id();
136 token_to_study_[token_id] = study_uid;
137
138 lock_token token;
139 token.token_id = token_id;
140 token.study_uid = study_uid;
141 token.type = lock_type::shared;
142 token.acquired_at = now;
143 token.expires_at = expiry;
144
145 record_acquisition(type);
146
147 if (on_lock_acquired_) {
148 lock_info info = existing.info;
149 info.token_id = token_id;
150 info.holder = resolved_holder;
151 lock.unlock();
152 on_lock_acquired_(study_uid, info);
153 }
154
156 }
157
158 // Cannot acquire lock - already locked
159 {
160 std::lock_guard stats_lock{stats_mutex_};
162 }
163
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)});
170 }
171
172 // Create new lock
173 const auto token_id = generate_token_id();
174
175 lock_entry entry;
176 entry.info.study_uid = study_uid;
177 entry.info.type = type;
178 entry.info.reason = reason;
179 entry.info.holder = resolved_holder;
180 entry.info.token_id = token_id;
181 entry.info.acquired_at = now;
182 entry.info.expires_at = expiry;
183 entry.info.shared_count = (type == lock_type::shared) ? 1 : 0;
184
185 if (type == lock_type::shared) {
186 entry.shared_holders.push_back(resolved_holder);
187 }
188
189 locks_[study_uid] = std::move(entry);
190 token_to_study_[token_id] = study_uid;
191
192 lock_token token;
193 token.token_id = token_id;
194 token.study_uid = study_uid;
195 token.type = type;
196 token.acquired_at = now;
197 token.expires_at = expiry;
198
199 record_acquisition(type);
200
201 if (on_lock_acquired_) {
202 lock_info info = locks_[study_uid].info;
203 lock.unlock();
204 on_lock_acquired_(study_uid, info);
205 }
206
208}
auto calculate_expiry(std::chrono::seconds timeout) const -> std::optional< std::chrono::system_clock::time_point >
Calculate expiration time.
void record_acquisition(lock_type type)
Update statistics on lock acquisition.
auto generate_token_id() const -> std::string
Generate a unique token ID.
constexpr int max_shared_exceeded
Maximum shared locks exceeded.
constexpr int already_locked
Lock already held by another holder.
auto to_string(lock_type type) -> std::string
Convert lock_type to string.
std::size_t contention_count
Number of lock contention events.

References kcenon::pacs::workflow::lock_info::acquired_at, kcenon::pacs::workflow::lock_token::acquired_at, kcenon::pacs::workflow::lock_error::already_locked, kcenon::pacs::workflow::lock_info::expires_at, kcenon::pacs::workflow::lock_token::expires_at, kcenon::pacs::workflow::lock_info::holder, kcenon::pacs::workflow::study_lock_manager::lock_entry::info, kcenon::pacs::workflow::lock_error::max_shared_exceeded, kcenon::pacs::workflow::lock_info::reason, kcenon::pacs::workflow::shared, kcenon::pacs::workflow::lock_info::shared_count, kcenon::pacs::workflow::study_lock_manager::lock_entry::shared_holders, kcenon::pacs::workflow::lock_info::study_uid, kcenon::pacs::workflow::lock_token::study_uid, kcenon::pacs::workflow::to_string(), kcenon::pacs::workflow::lock_info::token_id, kcenon::pacs::workflow::lock_token::token_id, kcenon::pacs::workflow::lock_info::type, and kcenon::pacs::workflow::lock_token::type.

Here is the call graph for this function:

◆ operator=() [1/2]

study_lock_manager & kcenon::pacs::workflow::study_lock_manager::operator= ( const study_lock_manager & )
delete

◆ operator=() [2/2]

study_lock_manager & kcenon::pacs::workflow::study_lock_manager::operator= ( study_lock_manager && other)
noexcept

Definition at line 49 of file study_lock_manager.cpp.

50 {
51 if (this != &other) {
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_);
61 }
62 return *this;
63}

◆ record_acquisition()

void kcenon::pacs::workflow::study_lock_manager::record_acquisition ( lock_type type)
private

Update statistics on lock acquisition.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 844 of file study_lock_manager.cpp.

844 {
845 std::lock_guard stats_lock{stats_mutex_};
847 (void)type; // Type-specific stats are calculated in get_stats()
848}
std::size_t total_acquisitions
Total locks acquired.

References stats_, stats_mutex_, and kcenon::pacs::workflow::lock_manager_stats::total_acquisitions.

◆ record_release()

void kcenon::pacs::workflow::study_lock_manager::record_release ( lock_type type,
std::chrono::milliseconds duration )
private

Update statistics on lock release.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 850 of file study_lock_manager.cpp.

852 {
853 std::lock_guard stats_lock{stats_mutex_};
855
856 // Update average duration
857 if (stats_.total_releases > 0) {
858 auto total_duration =
860 duration.count();
862 std::chrono::milliseconds{total_duration / stats_.total_releases};
863 }
864
865 // Update max duration
866 if (duration > stats_.max_lock_duration) {
867 stats_.max_lock_duration = duration;
868 }
869
870 (void)type; // Type-specific stats are calculated in get_stats()
871}
std::chrono::milliseconds avg_lock_duration
Average lock duration.
std::chrono::milliseconds max_lock_duration
Maximum lock duration observed.
std::size_t total_releases
Total locks released.

References kcenon::pacs::workflow::lock_manager_stats::avg_lock_duration, kcenon::pacs::workflow::lock_manager_stats::max_lock_duration, stats_, stats_mutex_, and kcenon::pacs::workflow::lock_manager_stats::total_releases.

◆ refresh_lock()

auto kcenon::pacs::workflow::study_lock_manager::refresh_lock ( const lock_token & token,
std::chrono::seconds extension = std::chrono::seconds{0} ) -> kcenon::common::Result<lock_token>
nodiscard

Refresh a lock (extend its timeout)

Parameters
tokenLock token to refresh
extensionAdditional time to extend the lock
Returns
Result containing updated lock_token or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 564 of file study_lock_manager.cpp.

567 {
568 std::unique_lock lock{mutex_};
569
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"});
577 }
578
579 auto lock_it = locks_.find(token_it->second);
580 if (lock_it == locks_.end()) {
582 kcenon::common::error_info{
584 "Lock not found",
585 "study_lock_manager"});
586 }
587
588 if (lock_it->second.info.is_expired()) {
590 kcenon::common::error_info{
592 "Lock has expired",
593 "study_lock_manager"});
594 }
595
596 // Calculate new expiry
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;
601 }
602
603 lock_token updated_token = token;
604 updated_token.expires_at = lock_it->second.info.expires_at;
605
606 return kcenon::common::Result<lock_token>::ok(updated_token);
607}
constexpr int expired
Lock has expired.
constexpr int invalid_token
Invalid token.

References kcenon::pacs::workflow::lock_error::expired, kcenon::pacs::workflow::lock_token::expires_at, kcenon::pacs::workflow::lock_error::invalid_token, and kcenon::pacs::workflow::lock_error::not_found.

◆ reset_stats()

void kcenon::pacs::workflow::study_lock_manager::reset_stats ( )

Reset statistics counters.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 752 of file study_lock_manager.cpp.

752 {
753 std::lock_guard stats_lock{stats_mutex_};
754 stats_ = lock_manager_stats{};
755}

References stats_, and stats_mutex_.

◆ resolve_holder()

auto kcenon::pacs::workflow::study_lock_manager::resolve_holder ( const std::string & holder) const -> std::string
nodiscardprivate

Get holder identifier (uses thread ID if empty)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 801 of file study_lock_manager.cpp.

802 {
803 if (!holder.empty()) {
804 return holder;
805 }
806
807 // Use thread ID as default holder
808 std::ostringstream oss;
809 oss << "thread_" << std::this_thread::get_id();
810 return oss.str();
811}

◆ set_config()

void kcenon::pacs::workflow::study_lock_manager::set_config ( const study_lock_manager_config & config)

Update configuration.

Note: Changes to some settings may not take effect immediately.

Parameters
configNew configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 762 of file study_lock_manager.cpp.

762 {
763 std::unique_lock lock{mutex_};
764 config_ = config;
765}

References config_, lock(), and mutex_.

Here is the call graph for this function:

◆ set_on_lock_acquired()

void kcenon::pacs::workflow::study_lock_manager::set_on_lock_acquired ( lock_event_callback callback)

Set callback for lock acquisition events.

Parameters
callbackCallback function
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 771 of file study_lock_manager.cpp.

771 {
772 on_lock_acquired_ = std::move(callback);
773}

References on_lock_acquired_.

◆ set_on_lock_expired()

void kcenon::pacs::workflow::study_lock_manager::set_on_lock_expired ( lock_event_callback callback)

Set callback for lock expiration events.

Parameters
callbackCallback function
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 779 of file study_lock_manager.cpp.

779 {
780 on_lock_expired_ = std::move(callback);
781}

References on_lock_expired_.

◆ set_on_lock_released()

void kcenon::pacs::workflow::study_lock_manager::set_on_lock_released ( lock_event_callback callback)

Set callback for lock release events.

Parameters
callbackCallback function
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 775 of file study_lock_manager.cpp.

775 {
776 on_lock_released_ = std::move(callback);
777}

References on_lock_released_.

◆ try_lock()

auto kcenon::pacs::workflow::study_lock_manager::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>
nodiscard

Try to acquire a lock without blocking.

Parameters
study_uidStudy UID to lock
typeType of lock to acquire
reasonReason for acquiring the lock
holderIdentifier of the lock holder
timeoutOptional timeout for the lock
Returns
Result containing lock_token or error if lock unavailable
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 210 of file study_lock_manager.cpp.

215 {
216 // try_lock is the same as lock for this implementation
217 // since we don't block waiting
218 return lock(study_uid, type, reason, holder, timeout);
219}

◆ unlock() [1/2]

auto kcenon::pacs::workflow::study_lock_manager::unlock ( const lock_token & token) -> kcenon::common::Result<std::monostate>
nodiscard

Release a lock using its token.

Parameters
tokenLock token received from lock()
Returns
Result indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 225 of file study_lock_manager.cpp.

226 {
227 std::unique_lock lock{mutex_};
228
229 // Find study by token
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"});
237 }
238
239 const auto study_uid = token_it->second;
240 auto lock_it = locks_.find(study_uid);
241 if (lock_it == locks_.end()) {
242 // Token exists but lock doesn't - clean up
243 token_to_study_.erase(token_it);
245 kcenon::common::error_info{
247 "Lock not found",
248 "study_lock_manager"});
249 }
250
251 auto& entry = lock_it->second;
252 const auto duration = entry.info.duration();
253 const auto type = entry.info.type;
254 lock_info released_info = entry.info;
255
256 // Handle shared lock release
257 if (type == lock_type::shared && entry.shared_holders.size() > 1) {
258 // Just remove this holder from shared list
259 auto holder_it = std::find(
260 entry.shared_holders.begin(),
261 entry.shared_holders.end(),
262 token.token_id); // Using token_id as holder identifier for shared
263
264 if (holder_it != entry.shared_holders.end()) {
265 entry.shared_holders.erase(holder_it);
266 }
267 token_to_study_.erase(token_it);
268 entry.info.shared_count = entry.shared_holders.size();
269
270 record_release(type, duration);
271
272 if (on_lock_released_) {
273 lock.unlock();
274 on_lock_released_(study_uid, released_info);
275 }
276
277 return kcenon::common::Result<std::monostate>::ok(std::monostate{});
278 }
279
280 // Remove lock entirely
281 token_to_study_.erase(token_it);
282 locks_.erase(lock_it);
283
284 record_release(type, duration);
285
286 if (on_lock_released_) {
287 lock.unlock();
288 on_lock_released_(study_uid, released_info);
289 }
290
291 return kcenon::common::Result<std::monostate>::ok(std::monostate{});
292}

References kcenon::pacs::workflow::lock_error::invalid_token, kcenon::pacs::workflow::lock_error::not_found, and kcenon::pacs::workflow::shared.

◆ unlock() [2/2]

auto kcenon::pacs::workflow::study_lock_manager::unlock ( const std::string & study_uid,
const std::string & holder ) -> kcenon::common::Result<std::monostate>
nodiscard

Release a lock by study UID and holder.

Parameters
study_uidStudy UID to unlock
holderIdentifier of the lock holder
Returns
Result indicating success or error

Definition at line 294 of file study_lock_manager.cpp.

297 {
298 const auto resolved_holder = resolve_holder(holder);
299
300 std::unique_lock lock{mutex_};
301
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"});
309 }
310
311 auto& entry = lock_it->second;
312
313 // Verify holder matches
314 if (entry.info.holder != resolved_holder) {
315 // Check shared holders
316 bool found_shared = false;
317 if (entry.info.type == lock_type::shared) {
318 auto holder_it = std::find(
319 entry.shared_holders.begin(),
320 entry.shared_holders.end(),
321 resolved_holder);
322 if (holder_it != entry.shared_holders.end()) {
323 found_shared = true;
324 entry.shared_holders.erase(holder_it);
325 entry.info.shared_count = entry.shared_holders.size();
326 }
327 }
328
329 if (!found_shared) {
331 kcenon::common::error_info{
333 "Lock held by different holder: " + entry.info.holder,
334 "study_lock_manager"});
335 }
336
337 if (!entry.shared_holders.empty()) {
338 // Other shared holders remain
339 record_release(entry.info.type, entry.info.duration());
340 return kcenon::common::Result<std::monostate>::ok(std::monostate{});
341 }
342 }
343
344 const auto duration = entry.info.duration();
345 const auto type = entry.info.type;
346 lock_info released_info = entry.info;
347
348 // Remove token mapping
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);
352 }
353
354 locks_.erase(lock_it);
355
356 record_release(type, duration);
357
358 if (on_lock_released_) {
359 lock.unlock();
360 on_lock_released_(study_uid, released_info);
361 }
362
363 return kcenon::common::Result<std::monostate>::ok(std::monostate{});
364}

References kcenon::pacs::workflow::lock_error::not_found, kcenon::pacs::workflow::lock_error::permission_denied, and kcenon::pacs::workflow::shared.

◆ unlock_all_by_holder()

auto kcenon::pacs::workflow::study_lock_manager::unlock_all_by_holder ( const std::string & holder) -> std::size_t

Release all locks held by a specific holder.

Parameters
holderIdentifier of the lock holder
Returns
Number of locks released
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 422 of file study_lock_manager.cpp.

423 {
424 const auto resolved_holder = resolve_holder(holder);
425 std::size_t count = 0;
426
427 std::unique_lock lock{mutex_};
428
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);
433 } else if (entry.info.type == lock_type::shared) {
434 // Check shared holders
435 auto it = std::find(
436 entry.shared_holders.begin(),
437 entry.shared_holders.end(),
438 resolved_holder);
439 if (it != entry.shared_holders.end()) {
440 entry.shared_holders.erase(it);
441 entry.info.shared_count = entry.shared_holders.size();
442 ++count;
443
444 if (entry.shared_holders.empty()) {
445 to_remove.push_back(study_uid);
446 }
447 }
448 }
449 }
450
451 for (const auto& study_uid : to_remove) {
452 auto it = locks_.find(study_uid);
453 if (it != locks_.end()) {
454 // Remove token mapping
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);
458 }
459
460 record_release(it->second.info.type, it->second.info.duration());
461 locks_.erase(it);
462 ++count;
463 }
464 }
465
466 return count;
467}

References kcenon::pacs::workflow::shared.

◆ validate_token()

auto kcenon::pacs::workflow::study_lock_manager::validate_token ( const lock_token & token) const -> bool
nodiscard

Validate a lock token.

Parameters
tokenLock token to validate
Returns
true if the token is valid and the lock is still held
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 544 of file study_lock_manager.cpp.

544 {
545 std::shared_lock lock{mutex_};
546
547 auto token_it = token_to_study_.find(token.token_id);
548 if (token_it == token_to_study_.end()) {
549 return false;
550 }
551
552 auto lock_it = locks_.find(token_it->second);
553 if (lock_it == locks_.end()) {
554 return false;
555 }
556
557 if (lock_it->second.info.is_expired()) {
558 return false;
559 }
560
561 return token_it->second == token.study_uid;
562}

Member Data Documentation

◆ config_

study_lock_manager_config kcenon::pacs::workflow::study_lock_manager::config_
private

◆ locks_

std::map<std::string, lock_entry> kcenon::pacs::workflow::study_lock_manager::locks_
private

◆ mutex_

std::shared_mutex kcenon::pacs::workflow::study_lock_manager::mutex_
mutableprivate

◆ next_token_id_

std::atomic<uint64_t> kcenon::pacs::workflow::study_lock_manager::next_token_id_ {1}
mutableprivate

◆ on_lock_acquired_

lock_event_callback kcenon::pacs::workflow::study_lock_manager::on_lock_acquired_
private

◆ on_lock_expired_

lock_event_callback kcenon::pacs::workflow::study_lock_manager::on_lock_expired_
private

◆ on_lock_released_

lock_event_callback kcenon::pacs::workflow::study_lock_manager::on_lock_released_
private

◆ stats_

lock_manager_stats kcenon::pacs::workflow::study_lock_manager::stats_
mutableprivate

◆ stats_mutex_

std::mutex kcenon::pacs::workflow::study_lock_manager::stats_mutex_
mutableprivate

◆ token_to_study_

std::map<std::string, std::string> kcenon::pacs::workflow::study_lock_manager::token_to_study_
private

Token to study UID mapping for fast lookup.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/workflow/study_lock_manager.h.

Definition at line 694 of file study_lock_manager.h.

Referenced by ~study_lock_manager().


The documentation for this class was generated from the following files: