Runner   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 12
lcom 1
cbo 3
dl 0
loc 139
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A run() 0 18 2
A handleError() 0 18 3
B acquireLock() 0 23 5
A releaseLock() 0 5 1
1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core
18
 * @filesource
19
 */
20
21
namespace Tenside\Core\Task;
22
23
use Psr\Log\LoggerInterface;
24
use Symfony\Component\Filesystem\LockHandler;
25
26
/**
27
 * This class runs a task.
28
 */
29
class Runner
30
{
31
    /**
32
     * The task to be run.
33
     *
34
     * @var Task
35
     */
36
    private $task;
37
38
    /**
39
     * The lock handler.
40
     *
41
     * @var LockHandler
42
     */
43
    private $lock;
44
45
    /**
46
     * The logger in use.
47
     *
48
     * @var LoggerInterface
49
     */
50
    private $logger;
51
52
    /**
53
     * The shutdown error handler shall be executed.
54
     *
55
     * @var bool
56
     */
57
    private $shutdownHandlerActive;
58
59
    /**
60
     * Create a new instance.
61
     *
62
     * @param Task            $task   The task to be run.
63
     *
64
     * @param LockHandler     $lock   The lock file to use.
65
     *
66
     * @param LoggerInterface $logger The logger to use.
67
     */
68
    public function __construct(Task $task, LockHandler $lock, LoggerInterface $logger)
69
    {
70
        $this->task   = $task;
71
        $this->lock   = $lock;
72
        $this->logger = $logger;
73
        register_shutdown_function([$this, 'handleError'], $this->task);
74
    }
75
76
    /**
77
     * Run the task.
78
     *
79
     * @param string $logfile The log file to use.
80
     *
81
     * @return bool
82
     */
83
    public function run($logfile)
84
    {
85
        $this->shutdownHandlerActive = true;
86
        $this->acquireLock();
87
88
        try {
89
            $this->task->perform($logfile);
90
        } catch (\Exception $exception) {
91
            $this->logger->error($exception->getMessage());
92
            $this->logger->error($this->task->getOutput());
93
        }
94
95
        $this->releaseLock();
96
97
        $this->shutdownHandlerActive = false;
98
99
        return Task::STATE_FINISHED === $this->task->getStatus();
100
    }
101
102
    /**
103
     * Called upon PHP shutdown.
104
     *
105
     * @return void
106
     */
107
    public function handleError()
108
    {
109
        if (!$this->shutdownHandlerActive) {
110
            return;
111
        }
112
113
        $error = error_get_last();
114
115
        if ($error['type'] === E_ERROR) {
116
            $message = sprintf('Error: "%s" in %s on %s', $error['message'], $error['file'], $error['line']);
117
            $this->task->markError();
118
            $this->task->addOutput($message);
119
            $this->logger->error($message);
120
        }
121
122
        // Ensure to release the lock.
123
        $this->releaseLock();
124
    }
125
126
    /**
127
     * Acquire the lock.
128
     *
129
     * @return void
130
     *
131
     * @throws \RuntimeException When the lock could not be acquired.
132
     */
133
    private function acquireLock()
134
    {
135
        $this->logger->info('Acquire lock file.');
136
137
        if (!$this->lock->lock()) {
138
            $locked = false;
139
            $retry  = 3;
140
            // Try up to 3 times to acquire with short delay in between.
141
            while ($retry > 0) {
142
                usleep(1000);
143
                if ($locked = $this->lock->lock()) {
144
                    break;
145
                }
146
                $retry--;
147
            }
148
            if (!$locked) {
149
                $this->logger->error('Could not acquire lock file.');
150
                throw new \RuntimeException(
151
                    'Another task appears to be running. If this is not the case, please remove the lock file.'
152
                );
153
            }
154
        }
155
    }
156
157
    /**
158
     * Release the lock.
159
     *
160
     * @return void
161
     */
162
    private function releaseLock()
163
    {
164
        $this->logger->info('Release lock file.');
165
        $this->lock->release();
166
    }
167
}
168