1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Database driver |
4
|
|
|
* |
5
|
|
|
* @author Matthias Gisder <[email protected]> |
6
|
|
|
* @copyright 2014 inGenerator Ltd |
7
|
|
|
* @licence BSD |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace Ingenerator\RunSingle; |
11
|
|
|
|
12
|
|
|
use \Ingenerator\RunSingle\PdoDatabaseObject; |
13
|
|
|
use \Psr\Log\LoggerInterface; |
14
|
|
|
|
15
|
|
|
class DbDriver implements LockDriver |
16
|
|
|
{ |
17
|
|
|
|
18
|
|
|
const DATE_FORMAT = 'd/m/Y H:i:s'; |
19
|
|
|
/** |
20
|
|
|
* @var \Ingenerator\RunSingle\PdoDatabaseObject |
21
|
|
|
*/ |
22
|
|
|
protected $db_object; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var callable |
26
|
|
|
*/ |
27
|
|
|
protected $timeProvider = 'time'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var \Psr\Log\LoggerInterface |
31
|
|
|
*/ |
32
|
|
|
protected $logger; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param \Ingenerator\RunSingle\PdoDatabaseObject $db_object |
36
|
|
|
*/ |
37
|
|
|
public function __construct(PdoDatabaseObject $db_object) |
38
|
|
|
{ |
39
|
|
|
$this->db_object = $db_object; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param callable $provider |
44
|
|
|
*/ |
45
|
|
|
public function set_time_provider($provider) |
46
|
|
|
{ |
47
|
|
|
$this->timeProvider = $provider; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @return int |
52
|
|
|
*/ |
53
|
|
|
protected function get_time() |
54
|
|
|
{ |
55
|
|
|
$time = \call_user_func($this->timeProvider); |
56
|
|
|
return $time; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param string $task_name |
61
|
|
|
* @param int $timeout |
62
|
|
|
* @param string $lock_holder |
63
|
|
|
* |
64
|
|
|
* @return false|integer |
65
|
|
|
* @throws \Exception |
66
|
|
|
* @throws \PDOException |
67
|
|
|
*/ |
68
|
|
|
public function get_lock($task_name, $timeout, $lock_holder) |
69
|
|
|
{ |
70
|
|
|
$timestamp = $this->get_time(); |
71
|
|
|
|
72
|
|
|
try { |
73
|
|
|
$this->db_object->execute('INSERT INTO '.$this->db_object->get_db_table_name()." VALUES(:task_name, :timestamp, :timeout, :lock_holder)", array( |
74
|
|
|
':task_name' => $task_name, |
75
|
|
|
':timestamp' => $timestamp, |
76
|
|
|
':timeout' => $timeout, |
77
|
|
|
':lock_holder' => $lock_holder, |
78
|
|
|
)); |
79
|
|
|
} catch (\PDOException $e) { |
80
|
|
|
if (\substr($e->getMessage(), 0, 69) === 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry') { |
81
|
|
|
return FALSE; |
82
|
|
|
} else { |
83
|
|
|
throw $e; |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
return $timestamp; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param string $task_name |
92
|
|
|
* |
93
|
|
|
* @return void |
94
|
|
|
*/ |
95
|
|
|
public function garbage_collect($task_name) |
96
|
|
|
{ |
97
|
|
|
$result = $this->db_object->fetch_all('SELECT * FROM '.$this->db_object->get_db_table_name().' WHERE task_name = :task_name AND (lock_timestamp + timeout) < :current_timestamp', array( |
98
|
|
|
':task_name' => $task_name, |
99
|
|
|
':current_timestamp' => $this->get_time() |
100
|
|
|
)); |
101
|
|
|
if (!$result) { |
|
|
|
|
102
|
|
|
$this->log('debug', 'no stale locks found for task '.$task_name); |
103
|
|
|
return; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$lock = $this->build_lock_object($result[0]); |
107
|
|
|
$this->log('warning', \sprintf('stale locks found for lock:'.PHP_EOL.' %s', $lock)); |
108
|
|
|
$this->release_lock($result[0]['task_name'], $result[0]['lock_timestamp']); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @param string $task_name |
113
|
|
|
* @param int $lock_timestamp |
114
|
|
|
* |
115
|
|
|
* @return void |
116
|
|
|
*/ |
117
|
|
|
public function release_lock($task_name, $lock_timestamp) |
118
|
|
|
{ |
119
|
|
|
$this->log('debug', 'releasing lock for task '.$task_name); |
120
|
|
|
$this->db_object->execute('DELETE FROM '.$this->db_object->get_db_table_name().' WHERE task_name = :task_name AND lock_timestamp = :lock_timestamp', array( |
121
|
|
|
':task_name' => $task_name, |
122
|
|
|
':lock_timestamp' => $lock_timestamp |
123
|
|
|
)); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param LoggerInterface $logger |
128
|
|
|
*/ |
129
|
|
|
public function set_logger(LoggerInterface $logger) |
130
|
|
|
{ |
131
|
|
|
$this->logger = $logger; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Log only if logger is set. |
136
|
|
|
* |
137
|
|
|
* @param $level |
138
|
|
|
* @param $message |
139
|
|
|
*/ |
140
|
|
|
protected function log($level, $message) |
141
|
|
|
{ |
142
|
|
|
if ($this->logger) { |
143
|
|
|
\call_user_func(array($this->logger, $level), $message); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @param array $result |
149
|
|
|
* |
150
|
|
|
* @return Lock |
151
|
|
|
*/ |
152
|
|
|
protected function build_lock_object($result) |
153
|
|
|
{ |
154
|
|
|
$data = array( |
155
|
|
|
'task_name' => $result['task_name'], |
156
|
|
|
'lock_id' => $result['lock_timestamp'], |
157
|
|
|
'timeout' => $result['timeout'], |
158
|
|
|
'lock_holder' => $result['lock_holder'], |
159
|
|
|
'expires' => new \DateTime('@'.($result['lock_timestamp'] + $result['timeout'])), |
160
|
|
|
'locked_at' => new \DateTime('@'.($result['lock_timestamp'])), |
161
|
|
|
); |
162
|
|
|
$lock_obj = new Lock($data); |
163
|
|
|
|
164
|
|
|
return $lock_obj; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Return a list of locks. |
169
|
|
|
* |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
|
|
public function list_locks() |
173
|
|
|
{ |
174
|
|
|
$result = $this->db_object->fetch_all('SELECT * FROM '.$this->db_object->get_db_table_name(), array()); |
175
|
|
|
|
176
|
|
|
$locks = array(); |
177
|
|
|
if ($result) { |
|
|
|
|
178
|
|
|
foreach ($result as $result_item) { |
179
|
|
|
$locks[] = $this->build_lock_object($result_item); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
return $locks; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
} |
187
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.