|
cluck 1.0.1
The cluster lock service.
|
Cluster lock. More...
#include <cluck.h>


Public Types | |
| typedef snapdev::callback_manager< callback_t > | callback_manager_t |
| typedef std::function< bool(cluck *)> | callback_t |
| typedef std::shared_ptr< cluck > | pointer_t |
| typedef std::uint64_t | serial_t |
Public Member Functions | |
| cluck (std::string const &object_name, ed::connection_with_send_message::pointer_t connection, ed::dispatcher::pointer_t dispatcher, mode_t mode=mode_t::CLUCK_MODE_SIMPLE) | |
| Create a cluster lock. | |
| virtual | ~cluck () override |
| Make sure to clean up the dispatcher. | |
| callback_manager_t::callback_id_t | add_finally_callback (callback_t func, callback_manager_t::priority_t priority=callback_manager_t::DEFAULT_PRIORITY) |
| Add a callback function to call when done with the lock. | |
| callback_manager_t::callback_id_t | add_lock_failed_callback (callback_t func, callback_manager_t::priority_t priority=callback_manager_t::DEFAULT_PRIORITY) |
| Add a callback function to call when the lock has failed. | |
| callback_manager_t::callback_id_t | add_lock_obtained_callback (callback_t func, callback_manager_t::priority_t priority=callback_manager_t::DEFAULT_PRIORITY) |
| Add a callback function to call when the lock is obtained. | |
| timeout_t | get_lock_duration_timeout () const |
| Retrieve the current lock duration. | |
| timeout_t | get_lock_obtention_timeout () const |
| Retrieve the current lock obtention duration. | |
| mode_t | get_mode () const |
| Retrieve the mode. | |
| std::string const & | get_object_name () const |
| Retrieve the object name. | |
| reason_t | get_reason () const |
| The reason for the last failure. | |
| timeout_t | get_timeout_date () const |
| Get the exact time when the lock times out. | |
| type_t | get_type () const |
| Retrieve the lock type. | |
| timeout_t | get_unlock_timeout () const |
| Retrieve the current unlock duration. | |
| bool | is_busy () const |
| Check whether the object is currently busy. | |
| bool | is_locked () const |
| This function checks whether the lock is considered locked. | |
| bool | lock () |
| Attempt a lock. | |
| virtual void | process_timeout () override |
| Process the timeout event. | |
| bool | remove_finally_callback (callback_manager_t::callback_id_t id) |
| Remove a callback function from the lock finally list. | |
| bool | remove_lock_failed_callback (callback_manager_t::callback_id_t id) |
| Remove a callback function from the lock failed list. | |
| bool | remove_lock_obtained_callback (callback_manager_t::callback_id_t id) |
| Remove a callback function from the lock obtained list. | |
| void | set_lock_duration_timeout (timeout_t timeout) |
| Set how long inter-process locks last. | |
| void | set_lock_obtention_timeout (timeout_t timeout) |
| Set how long to wait for an inter-process lock to take. | |
| void | set_type (type_t type) |
| Set the lock type. | |
| void | set_unlock_timeout (timeout_t timeout) |
| Set how long we wait on an inter-process unlock acknowledgement. | |
| void | unlock () |
| Release the inter-process lock. | |
Protected Member Functions | |
| virtual void | finally () |
| The lock cycle is finally complete. | |
| virtual void | lock_failed () |
| The lock did not take or an error was reported. | |
| virtual void | lock_obtained () |
| This function gets called whenever the lock is in effect. | |
Private Member Functions | |
| bool | help (advgetopt::string_set_t &commands) |
| Called whenever the HELP message is received or new messages are added. | |
| bool | is_cluck_msg (ed::message &msg) const |
| Verify a message we received. | |
| void | msg_lock_failed (ed::message &msg) |
| Process the LOCK_FAILED message. | |
| void | msg_locked (ed::message &msg) |
| Process the LOCKED message. | |
| void | msg_transmission_report (ed::message &msg) |
| Get a transmission report on errors. | |
| void | msg_unlocked (ed::message &msg) |
| Process the UNLOCK acknowledgement. | |
| void | msg_unlocking (ed::message &msg) |
| The cluckd service sent us an UNLOCKING message. | |
| void | set_reason (reason_t reason) |
| Change the reason why a lock failed. | |
Private Attributes | |
| ed::connection_with_send_message::pointer_t | f_connection = ed::connection_with_send_message::pointer_t() |
| ed::dispatcher::pointer_t | f_dispatcher = ed::dispatcher::pointer_t() |
| callback_manager_t | f_finally_callbacks = callback_manager_t() |
| timeout_t | f_lock_duration_timeout = CLUCK_DEFAULT_TIMEOUT |
| callback_manager_t | f_lock_failed_callbacks = callback_manager_t() |
| callback_manager_t | f_lock_obtained_callbacks = callback_manager_t() |
| timeout_t | f_lock_obtention_timeout = CLUCK_DEFAULT_TIMEOUT |
| timeout_t | f_lock_timeout_date = timeout_t() |
| mode_t | f_mode = mode_t::CLUCK_MODE_SIMPLE |
| std::string | f_object_name = std::string() |
| reason_t | f_reason = reason_t::CLUCK_REASON_NONE |
| serial_t | f_serial = serial_t() |
| state_t | f_state = state_t::CLUCK_STATE_IDLE |
| ed::dispatcher_match::tag_t const | f_tag = ed::dispatcher_match::DISPATCHER_MATCH_NO_TAG |
| type_t | f_type = type_t::CLUCK_TYPE_READ_WRITE |
| timeout_t | f_unlock_timeout = CLUCK_DEFAULT_TIMEOUT |
| timeout_t | f_unlocked_timeout_date = timeout_t() |
This class is used to run code synchronously in a cluster of computers.
The class accepts three callbacks:
The cluck class is itself an eventdispatcher connection so you must add it to the communicator. Actually, you can add it and then forget about the pointer. That way, once you are in your finally code, it automatically gets removed from the communicator.
| typedef snapdev::callback_manager<callback_t> cluck::cluck::callback_manager_t |
| typedef std::function<bool(cluck *)> cluck::cluck::callback_t |
| typedef std::shared_ptr<cluck> cluck::cluck::pointer_t |
| typedef std::uint64_t cluck::cluck::serial_t |
| cluck::cluck::cluck | ( | std::string const & | object_name, |
| ed::connection_with_send_message::pointer_t | messenger, | ||
| ed::dispatcher::pointer_t | dispatcher, | ||
| mode_t | mode = mode_t::CLUCK_MODE_SIMPLE |
||
| ) |
The cluck object expects at least three parameters to offer the ability to create a cluster lock. A cluster lock allows you to run one or more functions in a cluster safe manner (i.e. by a single process running in a computer cluster of any size).
First, the function expects a object_name parameter. This is the name of the lock. To run a function within a single process, any number of processes can request a lock using the exact same object_name.
Second, the cluck object needs to send messages and for the purposes needs to have access to a connection that has the send_message() functionality implemented. In most likelihood, this is your communicator derived class (your messenger).
Third, the function has to react to replies from the messages it sends. This is accomplished by the dispatcher object. Again, this is likely your messenger dispatcher.
The constructor collects that data. The lock() function sets up the dispatcher callbacks and sends a LOCK message to the connection. The unlock() function is implemented as a failure, your error callbacks are called and then the finally callbacks, which removes the dispatcher callbacks. (Note: there are two sets of callbacks, the dispatcher callbacks are automatically managed, the success, error, and finally callbacks are managed by you, the cluck user.)
Many functions cannot be called once the lock() function was called, including the lock() function itself. These work again after the finally() function was called. This is done this way because some of the cluck parameters cannot or should not be changed while a lock is being processed.
The dispatcher expects to receive one of the LOCKED or LOCKFAILED messages after the LOCK was sent to the cluck service. Once done with a lock, we send an UNLOCK message and in that case we expected the UNLOCKED message as a reply. If a processes uses a LOCK for too long (i.e. the cluck service notices that the lock timed out), it may also receive the UNLOCKING message letting it know that the lock is about to be terminated. Your cluck objects react to that message by stopping the lock and you are expected to not run any additional cluster safe code since it may become unsafe at any moment.
Keep in mind that the object is used 100% asynchronously. If you want to execute code after the lock was released, make sure to also define a set of finally callbacks.
If the code you need to execute requires sending and receiving other messages (i.e. go back in the communicator run() loop), then it is required to use the mode_t::CLUCK_MODE_EXTENDED mode. Otherwise, the lock gets released as soon as all your lock obtained callbacks return.
The cluck object uses default time out durations. These can be changed using the global functions provided for the purpose. It is also possible to change those values on a per cluck object before calling the lock() function.
The timer is disabled by default. Various functions enable it and set a timeout date to be able to track timeouts locally since we cannot be sure that the cluck service is even running at the time a cluck object tries to communicate with it. Please, do not attempt to use this timer for anything else as it would likely cause issues with this object implementation.
The following shows the preparation and demantlement of your client and the successful path of a cluck lock:
The following shows an unsuccessful lock:
| [in] | object_name | The name of the lock. |
| [in] | messenger | The connection used to send messages. |
| [in] | dispatcher | The dispatcher used to receive messages. |
| [in] | mode | Defines the usage of the lock. |
Definition at line 377 of file cluck.cpp.
References help().
|
overridevirtual |
| cluck::callback_manager_t::callback_id_t cluck::cluck::add_finally_callback | ( | callback_t | func, |
| callback_manager_t::priority_t | priority = callback_manager_t::DEFAULT_PRIORITY |
||
| ) |
This function adds a callback function to the callback manager used to register function called whenever the object is done with the lock.
| [in] | func | The callback function to call. |
| [in] | priority | The priority of the callback. |
| cluck::callback_manager_t::callback_id_t cluck::cluck::add_lock_failed_callback | ( | callback_t | func, |
| callback_manager_t::priority_t | priority = callback_manager_t::DEFAULT_PRIORITY |
||
| ) |
| cluck::callback_manager_t::callback_id_t cluck::cluck::add_lock_obtained_callback | ( | callback_t | func, |
| callback_manager_t::priority_t | priority = callback_manager_t::DEFAULT_PRIORITY |
||
| ) |
|
protectedvirtual |
This function is called once the lock process completes. This means a LOCK was sent and we either were able to do work while the lock was active or the lock failed and we received a call to the lock_failed() function instead.
By default, this function calls all the finally callbacks you added to this object. Make sure to call it if you use callbacks.
Inside the finally() function and callbacks, the state of the lock is "idle". This means you can call the lock() function immediately.
Definition at line 1348 of file cluck.cpp.
References cluck::CLUCK_STATE_IDLE.
| timeout_t cluck::cluck::get_lock_duration_timeout | ( | ) | const |
This function returns the current lock timeout. It can be useful if you want to use a lock with a different timeout and then restore the previous value afterward.
Although if you have access/control of the lock itself, you may instead want to specify the timeout in the snap_lock constructor directly.
| timeout_t cluck::cluck::get_lock_obtention_timeout | ( | ) | const |
This function returns the current timeout for the obtention of a lock. It can be useful if you want to use a lock with a different obtention timeout and then restore the previous value afterward.
Definition at line 523 of file cluck.cpp.
Referenced by lock().
| mode_t cluck::cluck::get_mode | ( | ) | const |
| std::string const & cluck::cluck::get_object_name | ( | ) | const |
| reason_t cluck::cluck::get_reason | ( | ) | const |
This function returns the reason of the last failure. Internally, we call the set_reason() to change this value.
Here are the reason for failure:
| timeout_t cluck::cluck::get_timeout_date | ( | ) | const |
This function is used to check when the current lock will be considerd out of date and thus when you should stop doing whatever requires said lock.
You can compare the time against snapdev::now() to know whether the lock timed out. Note that the is_locked() function also technically returns the same information.
Remember that this exact date was sent to the cluck service but you may have a clock with a one second or so difference between various computers so if the amount is really small (under 2 seconds) you should probably already considered that the lock has timed out.
| type_t cluck::cluck::get_type | ( | ) | const |
The lock can be given a type changing the behavior of the locking mechanism. Please see the set_type() function for additional details.
| timeout_t cluck::cluck::get_unlock_timeout | ( | ) | const |
|
private |
This function gets called whenever the dispatcher receives the HELP message. It then replies with the COMMANDS message.
The function adds the multiple commands that the cluck supports and which use a non-default match function.
| [in] | commands | The list of commands to complete. |
Definition at line 781 of file cluck.cpp.
Referenced by cluck().
| bool cluck::cluck::is_busy | ( | ) | const |
This function checks whether the object is busy. If so, then the lock() function cannot be called. It is expected that the object is and remains busy between the lock() call and the receival of the UNLOCKED message.
Definition at line 1130 of file cluck.cpp.
References cluck::CLUCK_STATE_IDLE.
|
private |
Many of the messages we receive have the exact same set of parameters. This function makes sure that these parameters are valid.
If the object_name or tag are not defined, or if the tag is not a match then this function returns false.
| invalid_parameter | If the object_name parameter is not set to this cluck f_object_name value, then this exception is raised. Note that if the object_name parameter or the tag parameters are not defined, the function instead sends an UNKNOWN message reply and then returns false. |
| [in] | msg | The message to verify. |
| bool cluck::cluck::is_locked | ( | ) | const |
This function checks whether the lock worked and is still considered active, as in, it did not yet time out.
This function does not access the network at all. It checks whether the lock is still valid using the current time and the time at which the LOCKED message said the lock would time out.
If you want to know whether cluck decided that the lock timed out then you need to consider calling the lock_timedout() function instead.
If you want to know how much time you have left on this lock, use the get_timeout_date() instead and subtract time(nullptr). If positive, that's the number of seconds you have left.
Definition at line 1108 of file cluck.cpp.
References cluck::CLUCK_STATE_LOCKED.
| bool cluck::cluck::lock | ( | ) |
This function attempts a lock. If a lock was already initiated or is in place, the function fails.
On a timeline, the lock obtention and lock duration parameters are used as follow:
If the lock obtention times out before we receive a LOCKED message back from the cluck service, the process calls your lock_failed() callback functions.
If the LOCKED message is received before the lock obtention times out, then your lock_obtained() callback functions get called.
The cluck service will send an UNLOCKING message once the lock duration is reached and no UNLOCK was received. At that point, continuing work is still safe, but considered dangerous. It is best to try to limit your work so it is complete by the time the lock end date is past.
If the cluck service never receives the UNLOCK message, it ends up sending a UNLOCKED message at the time the "unlock timeout" is reached.
Definition at line 871 of file cluck.cpp.
References cluck::CLUCK_DEFAULT_TIMEOUT, cluck::CLUCK_REASON_NONE, cluck::CLUCK_REASON_TRANSMISSION_ERROR, cluck::CLUCK_STATE_FAILED, cluck::CLUCK_STATE_LOCKING, cluck::CLUCK_TYPE_READ_WRITE, get_lock_obtention_timeout(), msg_lock_failed(), msg_locked(), msg_transmission_report(), msg_unlocked(), and msg_unlocking().
|
protectedvirtual |
When the LOCK message does not get a reply or gets a reply implying that it cannot be secured, this function gets called. You are free to override the function.
By default, this function calls all the lock failed callbacks you added to this object. Make sure to call it if you use callbacks.
Definition at line 1314 of file cluck.cpp.
References cluck::CLUCK_STATE_FAILED.
|
protectedvirtual |
This function is a signal telling you that the lock is in effect. You are free to override it, although, in most likelihood, you'll want to set a lock obtained callback instead.
If you override this function, you are responsible to call this base class version if you want the "lock obtained" callbacks to be called.
Further, the default function checks the cluck lock mode, if SIMPLE, it sends the UNLOCK immediately before returning. In other words, you probably will want to call this instance of the function after you did the work you intended to do while the lock is active.
Definition at line 1288 of file cluck.cpp.
References cluck::CLUCK_MODE_SIMPLE.
|
private |
This function processes the LOCK_FAILED message. This means the state of this cluck object becomes FAILED which means it cannot be used to obtain a lock again.
| [in] | msg | The LOCK_FAILED message. |
Definition at line 1398 of file cluck.cpp.
References cluck::CLUCK_REASON_INVALID, and cluck::CLUCK_REASON_REMOTE_TIMEOUT.
Referenced by lock().
|
private |
Whenever the client sends a LOCK message to the cluckd service, it expects a LOCKED reply which means the lock was obtained and is in place for this process to make use of.
The lock_obtained() function is called which in turn calls your lock_obtained callbacks.
| [in] | msg | The LOCKED message. |
Definition at line 1367 of file cluck.cpp.
References cluck::CLUCK_REASON_INVALID, and cluck::CLUCK_STATE_LOCKED.
Referenced by lock().
|
private |
If the LOCK message cannot be sent, we receive a transmission error. At the moment, there are two possible reasons:
In case of a plain failure, we cancel the whole process immediately.
In case the message was cached, we can ignored the report since the message will eventually make it so it is not an immediate failure. In most cases, if cached, the timeout will let us know if the message does not make it.
| [in] | msg | The TRANSMISSION_REPORT message. |
Definition at line 1450 of file cluck.cpp.
References cluck::CLUCK_REASON_TRANSMISSION_ERROR.
Referenced by lock().
|
private |
Once the lock of a process is unlocked, the UNLOCKED message is sent to that process. This function processes the acknowledgement which means calling the finally() function.
After this message was received the lock is idle again and can be re-obtained by calling the lock() function.
| [in] | msg | The message sent by the cluck service. |
Definition at line 1488 of file cluck.cpp.
References cluck::CLUCK_REASON_INVALID, cluck::CLUCK_REASON_NONE, and cluck::CLUCK_REASON_REMOTE_TIMEOUT.
Referenced by lock().
|
private |
Whenever the cluck daemon detects that a lock is about to be unlocked, it sends the UNLOCKING message to the lock holder. At that point, the client is expected to stop using the lock. This function actually sends the UNLOCK message to acknowledge receipt of this message.
The client is expected to check the is_locked() function as required to make sure it does not access an unlocked resource.
| [in] | msg | The UNLOCKING message. |
Definition at line 1529 of file cluck.cpp.
References cluck::CLUCK_REASON_INVALID, and cluck::CLUCK_REASON_REMOTE_TIMEOUT.
Referenced by lock().
|
overridevirtual |
This function processes a timeout event. Each time we send a message and require a timely reply, we start a timer to see whether it was indeed timely or not. If not, then this function gets called and we can proceed with canceling the request.
This is often referenced as the local timeout.
Definition at line 1207 of file cluck.cpp.
References cluck::CLUCK_REASON_LOCAL_TIMEOUT, cluck::CLUCK_STATE_FAILED, cluck::CLUCK_STATE_IDLE, cluck::CLUCK_STATE_LOCKED, cluck::CLUCK_STATE_LOCKING, and cluck::CLUCK_STATE_UNLOCKING.
| bool cluck::cluck::remove_finally_callback | ( | callback_manager_t::callback_id_t | id | ) |
This function is the converse of the add_finally_callback(). If you save the returned callback identifier, you can later call this function to remove the callback explicitly.
| [in] | id | The identifier of the callback to remove. |
| bool cluck::cluck::remove_lock_failed_callback | ( | callback_manager_t::callback_id_t | id | ) |
This function is the converse of the add_lock_failed_callback(). If you save the returned callback identifier, you can later call this function to remove the callback explicitly.
| [in] | id | The identifier of the callback to remove. |
| bool cluck::cluck::remove_lock_obtained_callback | ( | callback_manager_t::callback_id_t | id | ) |
This function is the converse of the add_lock_obtained_callback(). If you save the returned callback identifier, you can later call this function to remove the callback explicitly.
| [in] | id | The identifier of the callback to remove. |
| void cluck::cluck::set_lock_duration_timeout | ( | timeout_t | timeout | ) |
This function lets you change the default amount of time the inter-process locks last (i.e. their "Time To Live") in seconds.
For example, to keep locks for 1 hour, use 3600.
This value is used whenever a lock is created with its lock duration set to -1.
| [in] | timeout | The total number of seconds locks will last for by default. |
Definition at line 595 of file cluck.cpp.
References cluck::CLUCK_DEFAULT_TIMEOUT, cluck::CLUCK_MAXIMUM_TIMEOUT, and cluck::CLUCK_MINIMUM_TIMEOUT.
| void cluck::cluck::set_lock_obtention_timeout | ( | timeout_t | timeout | ) |
This function lets you change the default amount of time the inter-process locks can wait before forfeiting the obtention of a new lock.
This amount can generally remain pretty small. For example, you could say that you want to wait just 1 minute even though the lock you want to get will last 24 hours. This means, within one minute your process is told that the lock cannot be obtained. In other words, you cannot do the work you intended to do. If the lock is released within the 1 minute and you are next on the list, you get the lock and can proceed with the work you intended to do.
The default is five seconds which for a front end is already quite enormous.
| [in] | timeout | The number of microseconds to wait to obtain a lock. |
Definition at line 553 of file cluck.cpp.
References cluck::CLUCK_DEFAULT_TIMEOUT, cluck::CLUCK_LOCK_OBTENTION_MAXIMUM_TIMEOUT, and cluck::CLUCK_MINIMUM_TIMEOUT.
|
private |
This function saves the reason for the last failure. This can be useful to know how to proceed next. i.e. failure to obtain a lock may tell your application that waiting another 5 minutes before trying again is a possibility.
| [in] | reason | The reason why this cluck object failed getting a lock. |
| void cluck::cluck::set_type | ( | type_t | type | ) |
This function changes the type of lock the lock() function uses. It can only be called if the cluck object is not currently busy (i.e. before the lock() function is called).
The supported type of locks are:
| busy | If the cluck object is currently in use (trying to obtain a lock, has a lock, releasing a lock) then the busy exception is raised. |
| [in] | type | One of the type_t enumerations. |
| void cluck::cluck::set_unlock_timeout | ( | timeout_t | timeout | ) |
This function lets you change the default amount of time the inter-process unlock lasts (i.e. their "Time To Survive" after a lock time out) in microseconds.
For example, to allow your application to take up to 5 minutes to acknowledge a timed out lock, set this value to 300'000'000.
| [in] | timeout | The number of microseconds before timing out on unlock. |
Definition at line 633 of file cluck.cpp.
References cluck::CLUCK_DEFAULT_TIMEOUT, cluck::CLUCK_MAXIMUM_TIMEOUT, and cluck::CLUCK_UNLOCK_MINIMUM_TIMEOUT.
| void cluck::cluck::unlock | ( | ) |
This function releases this inter-process lock at the time it gets called. When everything works as expected, this function should be called before the lock times out.
If the lock was not active (i.e. lock() was never called or returned false last time you called it), then nothing happens.
This is useful if you keep the same lock object around and want to lock/unlock it at various time. It is actually very important to unlock your locks so other processes can then gain access to the resources that they protect.
Definition at line 995 of file cluck.cpp.
References cluck::CLUCK_REASON_TRANSMISSION_ERROR, cluck::CLUCK_STATE_FAILED, cluck::CLUCK_STATE_LOCKED, cluck::CLUCK_STATE_LOCKING, and cluck::CLUCK_STATE_UNLOCKING.
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
This document is part of the Snap! Websites Project.
Copyright by Made to Order Software Corp.