Test Failed
Push — trunk ( e02d87...314b8c )
by SuperNova.WS
07:20
created

Lock   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 50
dl 0
loc 164
rs 10
c 1
b 0
f 0
wmc 16

6 Methods

Rating   Name   Duplication   Size   Complexity  
A attemptLock() 0 41 6
A isLocked() 0 15 3
A lock() 0 5 1
A __construct() 0 9 1
A unLock() 0 6 3
A configWrite() 0 3 2
1
<?php
2
/**
3
 * Created by Gorlum 08.02.2020 16:42
4
 */
5
6
namespace Core\Scheduler;
7
8
use classConfig;
9
use Core\GlobalContainer;
10
use DBAL\db_mysql;
11
12
/**
13
 * Locking mechanic
14
 *
15
 * @package Core\Scheduler
16
 */
17
class Lock {
18
  /**
19
   * Skip task execution if task is locked but lock grace period is expired
20
   */
21
  const LOCK_EXPIRED_SKIP_TASK = 0;
22
  /**
23
   * Treat lock expiration as previous task fail and process task
24
   */
25
  const LOCK_EXPIRED_IGNORE = 1;
26
27
28
  /**
29
   * @var GlobalContainer $gc
30
   */
31
  protected $gc;
32
  /**
33
   * @var db_mysql
34
   */
35
  protected $db;
36
  /**
37
   * @var classConfig $config
38
   */
39
  protected $config;
40
41
  /**
42
   * Name for config lock field
43
   *
44
   * @var string $configName
45
   */
46
  protected $configName = '';
47
  /**
48
   * Interval to lock task
49
   *
50
   * @var int $maxLockInterval
51
   */
52
  protected $maxLockInterval = PERIOD_MINUTE;
53
  /**
54
   * Delta for lock to minimize chance of simulate access/lock race
55
   *
56
   * @var int $intervalDelta
57
   */
58
  protected $intervalDelta = 0;
59
  protected $lockType = classConfig::DATE_TYPE_SQL_STRING;
60
61
  /**
62
   * Lock constructor.
63
   *
64
   * @param GlobalContainer $gc
65
   * @param string          $configName
66
   * @param int             $maxLockInterval
67
   * @param int             $intervalDelta
68
   * @param int             $lockType
69
   */
70
  public function __construct($gc, $configName, $maxLockInterval = PERIOD_MINUTE, $intervalDelta = 10, $lockType = classConfig::DATE_TYPE_SQL_STRING) {
71
    $this->gc     = $gc;
72
    $this->db = $this->gc->db;
73
    $this->config = $this->gc->config;
74
75
    $this->configName      = $configName;
76
    $this->maxLockInterval = $maxLockInterval;
77
    $this->intervalDelta   = $intervalDelta;
78
    $this->lockType        = $lockType;
79
  }
80
81
  /**
82
   * Checking for lock
83
   *
84
   * @return int|null Lock time left. Negative if lock time passed. Lock released immediately if SN_TIME_NOW === lockTime
85
   */
86
  public function isLocked($time = SN_TIME_NOW) {
87
    $timeLeft = null;
88
    if ($this->configName) {
89
      $lockedUntil = $this->config->dateRead($this->configName, classConfig::DATE_TYPE_UNIX);
90
91
      if ($lockedUntil == 0) {
92
        // There is no current lock - nothing to do
93
        $timeLeft = null;
94
      } else {
95
        $timeLeft = $lockedUntil - $time;
96
      }
97
//var_dump('LOCK: ' . ($timeLeft !== null ? "lock present - left {$timeLeft}s" : 'no lock'));
98
    }
99
100
    return $timeLeft;
101
  }
102
103
  /**
104
   * Placing lock
105
   */
106
  public function lock($time = SN_TIME_NOW) {
107
    $toWrite = $time + mt_rand($this->maxLockInterval - $this->intervalDelta, $this->maxLockInterval + $this->intervalDelta);
108
    $this->configWrite($toWrite);
109
110
    return $this;
111
  }
112
113
  /**
114
   * Removing lock
115
   */
116
  public function unLock($selfTransaction = true) {
117
    $selfTransaction ? $this->db->transactionStart() : false;
118
    $this->configWrite(0);
119
    $selfTransaction ? $this->db->transactionCommit() : false;
120
121
    return $this;
122
  }
123
124
  /**
125
   * @param $data
126
   */
127
  protected function configWrite($data) {
128
    if ($this->configName) {
129
      $this->config->dateWrite($this->configName, $data, classConfig::DATE_TYPE_SQL_STRING);
130
    }
131
  }
132
133
  /**
134
   * Attempt to acquire lock
135
   *
136
   * @param callable|null $callable External function to determine how to react on lock expiration. On `null` - task is always unlocked on lock expired
137
   *
138
   * @return bool TRUE if lock was placed FALSE if lock was already enabled
139
   */
140
  public function attemptLock($callable = null, $time = SN_TIME_NOW) {
141
    $this->db->transactionStart();
142
    $lockTimeLeft = $this->isLocked($time);
143
144
    // Task is still locked
145
    if ($lockTimeLeft > 0 || $lockTimeLeft === 0) {
146
//var_dump('LOCK: Task still locked');
147
      $this->db->transactionRollback();
148
149
      return false;
150
    }
151
152
    // Task lock grace period expired but further processing is locked by caller
153
    if (
154
      $lockTimeLeft < 0
155
      &&
156
      (
157
        is_callable($callable)
158
        &&
159
        (($q = $callable()) === self::LOCK_EXPIRED_SKIP_TASK)
0 ignored issues
show
Unused Code introduced by
The assignment to $q is dead and can be removed.
Loading history...
160
      )
161
    ) {
162
//var_dump('LOCK: Task lock expired but task is skipped');
163
164
      // Then we just rolling back transaction and returning 'false'
165
      $this->db->transactionRollback();
166
167
      return false;
168
    }
169
//if (
170
//  $lockTimeLeft < 0){
171
//  var_dump('LOCK: lock expired - we should re-lock');
172
//}
173
//var_dump('LOCK: ' . ($lockTimeLeft > 0 ? 'task still locked' : ($lockTimeLeft < 0 ? 'lock expired, unlocking' : 'not locked')));
174
175
    // Placing task lock
176
    $this->lock();
177
178
    $this->db->transactionCommit();
179
180
    return true;
181
  }
182
183
}
184