Provides an unlimited set of named, atomicly incrementable/decrementable, counters using either the blindingly fast Redis deamon or a slower but more easily deployed filesystem implementation.
Q: Why would you need such a beastie?
A: This repository solves a problem that can arise where a unique ID needs to be determined before data is written to the database, such as using an incremental number as the page name in ProcessWire before the page is saved. It also provides an implementation of escrow purchases where there could be a race between competing buyers to secure a limited qty of some resource or product. For more information about this, please read escrow.md.
If two or more processor threads attempt to run the same routine at the same time (two or more customers raising a bug-track issue simultaneously for example), there is the possibility that they would be assigned the same next ID before the page was saved and one would result in an error (at best).
For a discussion of the technical aspects and gotcha's that lead to the development of Thread-safe counters, please read rationale.md
Thread-safe counters are...
- Fast: No need for round-trips to the DB to calculate or recalculate your id values.
- Portable: Works on PHP across OS platforms.
- Thread-safe: Guarantees a unique next value on every call.
- Transportable: The counter values can be moved along with your site if you need to migrate it.
- Flexible: Can increment or decrement by 1 or any value you like.
- Almost Unlimited: Use as many named counters as your application needs.
- Simple to use: As simple as calling
next('your_counter_name')
!
Two implementations are provided. The first is dependency free and uses PHP's built-in flock()
function in
blocking mode to implement a mutually exclusive section of code. The second uses Redis and its inherent single threading
to provide the needed mutual exclusion.
\
|- readme.md // This file.
|- rationale.md // Documentation.
|- escrow.md // Walks you through the escrow model supported by this code.
|
|- ThreadsafeCountersBase.php // Abstract base class implementing common methods.
|- ThreadsafeCountersFile.php // The filesystem-based implementation.
|- ThreadsafeCountersRedis.php // The Redis-based implementation.
|
|- ThreadsafeFileCounters.module // ProcessWire module that uses the ThreadsafeCountersFile.php.
|- ThreadsafeRedisCounters.module // ProcessWire module that uses the ThreadsafeCountersRedis.php.
Basic increment of a named counter...
require_once "ThreadsafeCountersBase.php";
require_once "ThreadsafeCountersFile.php";
$unique_transaction_id = ThreadsafeCountersFile::next('transaction_id');
Increment by a value other than 1...
$unique_transaction_id = ThreadsafeCountersFile::next('transaction_id', 10);
Because the file implementation makes use of files on disk to track the last value we need to be able to handle the case
of a file deletion. Deleting the counter file will reset the counter but if our application is tracking its last use of
the generated value (perhaps by storing it somewhere else) then we can have our next()
routine check that the next
value it generates is greater than the last noted value. If it isn't then the file has probably been deleted and we will
need to generate an id that is ahead of the last possible value.
$last_used_value = getLastUsedId();
$unique_transaction_id = ThreadsafeCountersFile::next('transaction_id', 1, $last_used_value);
Basic use of Redis implementation...
require_once "ThreadsafeCountersBase.php";
require_once "ThreadsafeCountersRedis.php";
$unique_transaction_id = ThreadsafeCountersRedis::next('transaction_id');
Increment by values greater than 1 is also possible...
$unique_transaction_id = ThreadsafeCountersRedis::next('transaction_id', 1000);
As with files, if the redis key being used to store the counter is deleted or changed to a value that has already been issued we can catch the problem and recover from it if we are keeping a track of the values already issued.
$last_used_value = getLastUsedId();
$unique_transaction_id = ThreadsafeCountersRedis::next('transaction_id', 1, $last_used_value);
It is up to your application to track the last used Id in some way (perhaps it is being used as a key in a DB column!)
There are module files provided for users of the ProcessWire CMF/CMS platform. One each for the files and Redis implementations.
The files implementation creates a new directory for your counters; "assets/counters" and stores all its counter files in there. As long as your assets/ directory is writable this should all work straight away. You can check the module settings when it is installed as it will tell you about any corrective actions needed.
The Redis module allows you to configure the IPv4 address and port of the redis server you wish to store your counters on. Set these up as required.
To use the counters from your template files you need to load the counter module and then call the next()
method.
$countserver = $modules->get('ThreadsafeRedisCounters'); // Use 'ThreadsafeFileCounters' to use files instead.
$next_id = $countserver->next('your_counter_name');
Copyright (c) 2014 Netcarver
The above copyright notice and the following permissions and disclaimer shall be included in all copies or substantial portions of the Software.
DO NOT DISTRIBUTE OTHER THAN ACCORDING TO THE TERMS OF YOUR LICENSE FILE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.