PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
study_lock_manager.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
19#pragma once
20
21#include <atomic>
22#include <chrono>
23#include <cstdint>
24#include <functional>
25#include <map>
26#include <memory>
27#include <mutex>
28#include <optional>
29#include <shared_mutex>
30#include <string>
31#include <vector>
32
33// Forward declarations for kcenon ecosystem
34namespace kcenon::common {
35template <typename T>
36class Result;
37struct error_info;
38} // namespace kcenon::common
39
40namespace kcenon::pacs::workflow {
41
42// =============================================================================
43// Lock Types and Configuration
44// =============================================================================
45
49enum class lock_type {
50 exclusive,
51 shared,
53};
54
58[[nodiscard]] inline auto to_string(lock_type type) -> std::string {
59 switch (type) {
60 case lock_type::exclusive: return "exclusive";
61 case lock_type::shared: return "shared";
62 case lock_type::migration: return "migration";
63 default: return "unknown";
64 }
65}
66
70[[nodiscard]] inline auto parse_lock_type(const std::string& str)
71 -> std::optional<lock_type> {
72 if (str == "exclusive") return lock_type::exclusive;
73 if (str == "shared") return lock_type::shared;
74 if (str == "migration") return lock_type::migration;
75 return std::nullopt;
76}
77
78// =============================================================================
79// Lock Token and Information
80// =============================================================================
81
85struct lock_token {
87 std::string token_id;
88
90 std::string study_uid;
91
94
96 std::chrono::system_clock::time_point acquired_at;
97
99 std::optional<std::chrono::system_clock::time_point> expires_at;
100
104 [[nodiscard]] auto is_valid() const -> bool {
105 if (!expires_at) return true;
106 return std::chrono::system_clock::now() < *expires_at;
107 }
108
112 [[nodiscard]] auto is_expired() const -> bool {
113 return !is_valid();
114 }
115
119 [[nodiscard]] auto remaining_time() const
120 -> std::optional<std::chrono::milliseconds> {
121 if (!expires_at) return std::nullopt;
122 auto now = std::chrono::system_clock::now();
123 if (now >= *expires_at) return std::chrono::milliseconds{0};
124 return std::chrono::duration_cast<std::chrono::milliseconds>(
125 *expires_at - now);
126 }
127};
128
132struct lock_info {
134 std::string study_uid;
135
138
140 std::string reason;
141
143 std::string holder;
144
146 std::string token_id;
147
149 std::chrono::system_clock::time_point acquired_at;
150
152 std::optional<std::chrono::system_clock::time_point> expires_at;
153
155 std::size_t shared_count{0};
156
160 [[nodiscard]] auto duration() const -> std::chrono::milliseconds {
161 return std::chrono::duration_cast<std::chrono::milliseconds>(
162 std::chrono::system_clock::now() - acquired_at);
163 }
164
168 [[nodiscard]] auto is_expired() const -> bool {
169 if (!expires_at) return false;
170 return std::chrono::system_clock::now() >= *expires_at;
171 }
172};
173
174// =============================================================================
175// Lock Manager Configuration
176// =============================================================================
177
183 std::chrono::seconds default_timeout{0};
184
186 std::chrono::milliseconds acquire_wait_timeout{5000};
187
189 std::chrono::seconds cleanup_interval{60};
190
192 bool auto_cleanup{true};
193
195 std::size_t max_shared_locks{100};
196
199};
200
201// =============================================================================
202// Lock Statistics
203// =============================================================================
204
210 std::size_t active_locks{0};
211
213 std::size_t exclusive_locks{0};
214
216 std::size_t shared_locks{0};
217
219 std::size_t migration_locks{0};
220
222 std::size_t total_acquisitions{0};
223
225 std::size_t total_releases{0};
226
228 std::size_t timeout_count{0};
229
231 std::size_t force_unlock_count{0};
232
234 std::chrono::milliseconds avg_lock_duration{0};
235
237 std::chrono::milliseconds max_lock_duration{0};
238
240 std::size_t contention_count{0};
241};
242
243// =============================================================================
244// Error Codes
245// =============================================================================
246
250namespace lock_error {
252 constexpr int already_locked = -100;
253
255 constexpr int not_found = -101;
256
258 constexpr int invalid_token = -102;
259
261 constexpr int timeout = -103;
262
264 constexpr int expired = -104;
265
267 constexpr int permission_denied = -105;
268
270 constexpr int invalid_type = -106;
271
273 constexpr int max_shared_exceeded = -107;
274
276 constexpr int upgrade_failed = -108;
277} // namespace lock_error
278
279// =============================================================================
280// Study Lock Manager Class
281// =============================================================================
282
338public:
339 // =========================================================================
340 // Construction
341 // =========================================================================
342
347
353 explicit study_lock_manager(const study_lock_manager_config& config);
354
359
363
366 study_lock_manager& operator=(study_lock_manager&&) noexcept;
367
368 // =========================================================================
369 // Lock Acquisition
370 // =========================================================================
371
381 [[nodiscard]] auto lock(
382 const std::string& study_uid,
383 const std::string& reason,
384 const std::string& holder = "",
385 std::chrono::seconds timeout = std::chrono::seconds{0})
387
398 [[nodiscard]] auto lock(
399 const std::string& study_uid,
400 lock_type type,
401 const std::string& reason,
402 const std::string& holder = "",
403 std::chrono::seconds timeout = std::chrono::seconds{0})
405
416 [[nodiscard]] auto try_lock(
417 const std::string& study_uid,
418 lock_type type,
419 const std::string& reason,
420 const std::string& holder = "",
421 std::chrono::seconds timeout = std::chrono::seconds{0})
423
424 // =========================================================================
425 // Lock Release
426 // =========================================================================
427
434 [[nodiscard]] auto unlock(const lock_token& token)
436
444 [[nodiscard]] auto unlock(
445 const std::string& study_uid,
446 const std::string& holder)
448
456 [[nodiscard]] auto force_unlock(
457 const std::string& study_uid,
458 const std::string& admin_reason = "")
460
467 auto unlock_all_by_holder(const std::string& holder) -> std::size_t;
468
469 // =========================================================================
470 // Lock Status
471 // =========================================================================
472
479 [[nodiscard]] auto is_locked(const std::string& study_uid) const -> bool;
480
488 [[nodiscard]] auto is_locked(
489 const std::string& study_uid,
490 lock_type type) const -> bool;
491
498 [[nodiscard]] auto get_lock_info(const std::string& study_uid) const
499 -> std::optional<lock_info>;
500
507 [[nodiscard]] auto get_lock_info_by_token(const std::string& token_id) const
508 -> std::optional<lock_info>;
509
516 [[nodiscard]] auto validate_token(const lock_token& token) const -> bool;
517
525 [[nodiscard]] auto refresh_lock(
526 const lock_token& token,
527 std::chrono::seconds extension = std::chrono::seconds{0})
529
530 // =========================================================================
531 // Lock Queries
532 // =========================================================================
533
539 [[nodiscard]] auto get_all_locks() const -> std::vector<lock_info>;
540
547 [[nodiscard]] auto get_locks_by_holder(const std::string& holder) const
548 -> std::vector<lock_info>;
549
556 [[nodiscard]] auto get_locks_by_type(lock_type type) const
557 -> std::vector<lock_info>;
558
564 [[nodiscard]] auto get_expired_locks() const -> std::vector<lock_info>;
565
566 // =========================================================================
567 // Maintenance
568 // =========================================================================
569
575 auto cleanup_expired_locks() -> std::size_t;
576
582 [[nodiscard]] auto get_stats() const -> lock_manager_stats;
583
587 void reset_stats();
588
594 [[nodiscard]] auto get_config() const -> const study_lock_manager_config&;
595
603 void set_config(const study_lock_manager_config& config);
604
605 // =========================================================================
606 // Event Callbacks
607 // =========================================================================
608
610 using lock_event_callback = std::function<void(
611 const std::string& study_uid,
612 const lock_info& info)>;
613
620
627
634
635private:
636 // =========================================================================
637 // Internal Types
638 // =========================================================================
639
640 struct lock_entry {
642 std::vector<std::string> shared_holders; // For shared locks
643 };
644
645 // =========================================================================
646 // Internal Methods
647 // =========================================================================
648
652 [[nodiscard]] auto generate_token_id() const -> std::string;
653
657 [[nodiscard]] auto resolve_holder(const std::string& holder) const
658 -> std::string;
659
663 [[nodiscard]] auto calculate_expiry(std::chrono::seconds timeout) const
664 -> std::optional<std::chrono::system_clock::time_point>;
665
669 [[nodiscard]] auto can_acquire_lock(
670 const std::string& study_uid,
671 lock_type type) const -> bool;
672
676 void record_acquisition(lock_type type);
677
681 void record_release(lock_type type, std::chrono::milliseconds duration);
682
683 // =========================================================================
684 // Member Variables
685 // =========================================================================
686
689
691 std::map<std::string, lock_entry> locks_;
692
694 std::map<std::string, std::string> token_to_study_;
695
697 mutable std::shared_mutex mutex_;
698
701
703 mutable std::mutex stats_mutex_;
704
706 mutable std::atomic<uint64_t> next_token_id_{1};
707
712};
713
714} // namespace kcenon::pacs::workflow
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.
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.
study_lock_manager(const study_lock_manager &)=delete
Non-copyable.
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.
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.
void set_config(const study_lock_manager_config &config)
Update configuration.
std::shared_mutex mutex
Mutex for thread-safe access.
constexpr int max_shared_exceeded
Maximum shared locks exceeded.
constexpr int permission_denied
Permission denied (force unlock not allowed)
constexpr int upgrade_failed
Cannot upgrade lock (shared to exclusive)
constexpr int invalid_type
Invalid lock type.
constexpr int expired
Lock has expired.
constexpr int already_locked
Lock already held by another holder.
constexpr int invalid_token
Invalid token.
constexpr int timeout
Lock timeout exceeded.
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.
auto parse_lock_type(const std::string &str) -> std::optional< lock_type >
Parse lock_type from 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.
auto is_expired() const -> bool
Check if the lock has expired.
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)
auto duration() const -> std::chrono::milliseconds
Get lock duration.
Statistics for lock manager operations.
std::size_t force_unlock_count
Locks that were forcibly released.
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 contention_count
Number of lock contention events.
std::size_t total_releases
Total locks released.
std::size_t timeout_count
Lock acquisitions that timed out.
Unique identifier for a lock.
std::string token_id
Unique token ID.
std::string study_uid
Study UID that is locked.
auto is_expired() const -> bool
Check if the token has expired.
std::optional< std::chrono::system_clock::time_point > expires_at
When the lock expires (if timeout set)
auto remaining_time() const -> std::optional< std::chrono::milliseconds >
Get remaining time until expiration.
auto is_valid() const -> bool
Check if the token is valid (not expired)
std::chrono::system_clock::time_point acquired_at
When the lock was acquired.
lock_type type
Type of lock held.
lock_info info
std::vector< std::string > shared_holders
Configuration for the study lock manager.
std::chrono::seconds default_timeout
Default lock timeout (0 = no timeout)
std::size_t max_shared_locks
Maximum number of concurrent shared locks.
std::chrono::seconds cleanup_interval
How often to check for expired locks.
bool allow_force_unlock
Allow force unlock for admin operations.
bool auto_cleanup
Enable automatic cleanup of expired locks.
std::chrono::milliseconds acquire_wait_timeout
Maximum time to wait when trying to acquire a lock.