Copyright | (c) 2016, Drew Hess |
---|---|
License | BSD3 |
Maintainer | Drew Hess <src@drewhess.com> |
Stability | experimental |
Portability | non-portable |
Safe Haskell | Safe |
Language | Haskell2010 |
This module implements a thread-safe, asynchronous controller. Scheduled locks are run as background threads, which sleep until their events fire.
Exception safety
All the controller actions provided in this module are exception-safe. If an exception occurs in a controller action (e.g., because the device throws an exception), the controller will be restored to its state as it was immediately prior to the execution of the action, and the exception will be re-thrown. After handling the exception, you can continue to execute actions on the controller, if you wish. However, the controller and the device may be out of sync at that point, or the device may continue to throw exceptions until it can be reset.
The safest action to take after an exception occurs in a controller is to reset the device to a known working state; and then to create, from scratch, a new controller for the device.
- data Controller d
- controller :: MonadIO m => Maybe NominalDiffTime -> Device d -> m (Controller d)
- minUnlockTime :: Controller d -> NominalDiffTime
- lockController :: MonadIO m => Controller d -> m State
- unlockController :: MonadIO m => UTCTime -> Controller d -> m State
- queryController :: MonadIO m => Controller d -> m State
- data Device d = Device {
- lockDevice :: IO ()
- unlockDevice :: IO ()
- data State
An asynchronous controller implementation
data Controller d Source #
A concurrent, thread-safe controller type parameterized on its device type.
Note that the type's constructor is not exported. You must use the
controller
constructor to create a new value of this type; it
ensures that the controller is initialized properly.
controller :: MonadIO m => Maybe NominalDiffTime -> Device d -> m (Controller d) Source #
Create a new Controller
value to control the given Device
.
Controllers created by this constructor are thread-safe and may be
passed around and controlled simultaneously on multiple threads.
All actions exported by this module which act on a Controller
value are thread-safe.
The controller locks and unlocks the given device in response to
user commands and expiring unlocks. The controller assumes that
this device has already been initialized and is ready for
operation. It also assumes that it exclusively owns the device; do
not pass the device to any other controllers or otherwise attempt
to control the device while the returned Controller
value is
live.
The controller treats the device as a critical section; only one thread at a time will issue operations to the device.
In order to synchronize the current device state with the state
machine, the constructor will lock the device and set the state
machine's initial state to StateLocked
before returning the new
Controller
value.
The optional NominalDiffTime
argument can be used to prevent the
device from too rapidly switching from the locked->unlocked->locked
states (glitching). Effectively, it specifies the minimum amount
of time that the controller will unlock the device. This is useful
for handling delayed unlock commands (for example, if the user is
communicating with the controller via a network connection but the
unlock command is delayed in transit because connection is down or
lagged), extremely short unlock durations that might damage the
physical access device, or hacking attempts. When the controller
receives an unlock command, it compares the current time to the
unlock command's expiration date. If the difference between the two
times is less than the minimum unlock duration, or if the
expiration date is in the past, then the controller will
effectively ignore the unlock request. If the value of this
argument is Nothing
or is negative, the controller treats it as a
0 value.
minUnlockTime :: Controller d -> NominalDiffTime Source #
Get the controller's minimum unlock time.
lockController :: MonadIO m => Controller d -> m State Source #
Immediately lock the device controlled by the controller.
Returns the new state of the controller.
unlockController :: MonadIO m => UTCTime -> Controller d -> m State Source #
Immediately unlock the device controlled by the controller, and
keep it unlocked until the specified UTCTime
.
If the specified time is in the past, then the device will unlock briefly, and then lock again after a brief amount of time. (NOTE: this behavior is considered to be a bug and will be fixed in a subsequent release.)
Returns the new state of the controller.
queryController :: MonadIO m => Controller d -> m State Source #
Query the controller's current state.
Re-exported types
A parametric device type which provides two "methods," one to lock the device, and the other to unlock it.
The parameter d
is the concrete device type and is used during
construction to create the two methods by binding them to actions
on the specific device.
For example, the implementation of the mockLockDevice
function,
which wraps a MockLock
in a Device
d
, looks like this:
mockLockDevice :: MockLock -> Device MockLock mockLockDevice l = Device (liftIO $ lockMockLock l) (liftIO $ unlockMockLock l)
A program can construct such a device and use it like so:
>>>
ml <- mockLock
>>>
let mld = mockLockDevice ml
>>>
events ml
[]>>>
lockDevice mld
>>>
events ml
[LockEvent ... UTC]>>>
unlockDevice mld
>>>
events ml
[LockEvent ... UTC,UnlockEvent ... UTC]
Device | |
|