Issues (1369)

classes/Core/Scheduler/Lock.php (1 issue)

Severity
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
    if ($selfTransaction) {
118
      $this->db->transactionStart();
119
    }
120
    $this->configWrite(0);
121
    if ($selfTransaction) {
122
      $this->db->transactionCommit();
123
    }
124
125
    return $this;
126
  }
127
128
  /**
129
   * @param $data
130
   */
131
  protected function configWrite($data) {
132
    if ($this->configName) {
133
      $this->config->dateWrite($this->configName, $data, classConfig::DATE_TYPE_SQL_STRING);
134
    }
135
  }
136
137
  /**
138
   * Attempt to acquire lock
139
   *
140
   * @param callable|null $callable External function to determine how to react on lock expiration. On `null` - task is always unlocked on lock expired
141
   *
142
   * @return bool TRUE if lock was placed FALSE if lock was already enabled
143
   */
144
  public function attemptLock($callable = null, $time = SN_TIME_NOW) {
145
    $this->db->transactionStart();
146
    $lockTimeLeft = $this->isLocked($time);
147
148
    // Task is still locked
149
    if ($lockTimeLeft > 0 || $lockTimeLeft === 0) {
150
//var_dump('LOCK: Task still locked');
151
      $this->db->transactionRollback();
152
153
      return false;
154
    }
155
156
    // Task lock grace period expired but further processing is locked by caller
157
    if (
158
      $lockTimeLeft < 0
159
      &&
160
      (
161
        is_callable($callable)
162
        &&
163
        (($q = $callable()) === self::LOCK_EXPIRED_SKIP_TASK)
0 ignored issues
show
The assignment to $q is dead and can be removed.
Loading history...
164
      )
165
    ) {
166
//var_dump('LOCK: Task lock expired but task is skipped');
167
168
      // Then we just rolling back transaction and returning 'false'
169
      $this->db->transactionRollback();
170
171
      return false;
172
    }
173
//if (
174
//  $lockTimeLeft < 0){
175
//  var_dump('LOCK: lock expired - we should re-lock');
176
//}
177
//var_dump('LOCK: ' . ($lockTimeLeft > 0 ? 'task still locked' : ($lockTimeLeft < 0 ? 'lock expired, unlocking' : 'not locked')));
178
179
    // Placing task lock
180
    $this->lock();
181
182
    $this->db->transactionCommit();
183
184
    return true;
185
  }
186
187
}
188