Test Failed
Push — main ( d01f4c...5af89f )
by Bingo
15:56
created

Timer::scheduleAtFixedRate()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
dl 0
loc 15
rs 9.2222
c 1
b 0
f 0
cc 6
nc 6
nop 3
1
<?php
2
3
namespace Jabe\Engine\Impl\Util\Timer;
4
5
class Timer
6
{
7
    /**
8
     * The timer task queue.  This data structure is shared with the timer
9
     * thread.  The timer produces tasks, via its various schedule calls,
10
     * and the timer thread consumes, executing timer tasks as appropriate,
11
     * and removing them from the queue when they're obsolete.
12
     */
13
    private $queue;
14
15
    /**
16
     * The timer thread.
17
     */
18
    private $thread;
19
20
    /**
21
     * This object causes the timer's task execution thread to exit
22
     * gracefully when there are no live references to the Timer object and no
23
     * tasks in the timer queue.  It is used in preference to a finalizer on
24
     * Timer as such a finalizer would be susceptible to a subclass's
25
     * finalizer forgetting to call it.
26
     */
27
    /*private final Object threadReaper = new Object() {
28
        protected void finalize() throws Throwable {
29
            synchronized(queue) {
30
                thread.newTasksMayBeScheduled = false;
31
                queue.notify(); // In case queue is empty.
32
            }
33
        }
34
    };*/
35
36
    /**
37
     * This ID is used to generate thread names.
38
     */
39
    private static $nextSerialNumber;
40
41
    private static function serialNumber(): int
42
    {
43
        if (self::$nextSerialNumber == null) {
44
            self::$nextSerialNumber = new AtomicInteger(0);
45
        }
46
        return self::$nextSerialNumber->getAndIncrement();
47
    }
48
49
    /**
50
     * Creates a new timer whose associated thread has the specified name.
51
     * The associated thread does <i>not</i>
52
     * {@linkplain Thread#setDaemon run as a daemon}.
53
     *
54
     * @param name the name of the associated thread
0 ignored issues
show
Bug introduced by
The type Jabe\Engine\Impl\Util\Timer\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
55
     * @throws NullPointerException if {@code name} is null
56
     * @since 1.5
57
     */
58
    public function __construct(string $name = null)
59
    {
60
        $this->queue = new TaskQueue();
61
        $this->thread = new TimerThread($this->queue);
62
        if ($name == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $name of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
63
            $name = "Timer-" . self::serialNumber();
64
        }
65
        $this->thread->setName($name);
0 ignored issues
show
Bug introduced by
The method setName() does not exist on Jabe\Engine\Impl\Util\Timer\TimerThread. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
        $this->thread->/** @scrutinizer ignore-call */ 
66
                       setName($name);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
66
        $this->thread->start();
67
    }
68
69
    /**
70
     * Schedules the specified task for execution after the specified delay.
71
     *
72
     * @param task  task to be scheduled.
73
     * @param delay delay in milliseconds before task is to be executed.
0 ignored issues
show
Bug introduced by
The type Jabe\Engine\Impl\Util\Timer\delay was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
74
     * @throws IllegalArgumentException if <tt>delay</tt> is negative, or
75
     *         <tt>delay + System.currentTimeMillis()</tt> is negative.
76
     * @throws IllegalStateException if task was already scheduled or
77
     *         cancelled, timer was cancelled, or timer thread terminated.
78
     * @throws NullPointerException if {@code task} is null
79
     */
80
    public function schedule(TimerTask $task, $delayOrDatetime, $period = null): void
81
    {
82
        if (is_int($delayOrDatetime) && $period == null) {
83
            if ($delayOrDatetime < 0) {
84
                throw new \Exception("Negative delay.");
85
            } else {
86
                $this->sched($task, floor(microtime(true) * 1000) + $delayOrDatetime, 0);
0 ignored issues
show
Bug introduced by
floor(microtime(true) * 1000) + $delayOrDatetime of type double is incompatible with the type integer expected by parameter $time of Jabe\Engine\Impl\Util\Timer\Timer::sched(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

86
                $this->sched($task, /** @scrutinizer ignore-type */ floor(microtime(true) * 1000) + $delayOrDatetime, 0);
Loading history...
87
            }
88
        } elseif ($delayOrDatetime instanceof \DateTime && $period == null) {
89
            $this->sched($task, $delayOrDatetime->getTimestamp() * 1000, 0);
90
        } elseif (is_int($delayOrDatetime) && is_int($period)) {
91
            if ($delayOrDatetime < 0) {
92
                throw new \Exception("Negative delay.");
93
            }
94
            if ($period <= 0) {
95
                throw new \Exception("Non-positive period.");
96
            }
97
            $this->sched($task, floor(microtime(true) * 1000) + $delayOrDatetime, -$period);
98
        } elseif ($delayOrDatetime instanceof \DateTime && is_int($period)) {
99
            if ($period <= 0) {
100
                throw new \Exception("Non-positive period.");
101
            }
102
            $this->sched($task, $delayOrDatetime->getTimestamp() * 1000, -$period);
103
        }
104
    }
105
106
    /**
107
     * Schedules the specified task for repeated <i>fixed-rate execution</i>,
108
     * beginning after the specified delay.  Subsequent executions take place
109
     * at approximately regular intervals, separated by the specified period.
110
     *
111
     * <p>In fixed-rate execution, each execution is scheduled relative to the
112
     * scheduled execution time of the initial execution.  If an execution is
113
     * delayed for any reason (such as garbage collection or other background
114
     * activity), two or more executions will occur in rapid succession to
115
     * "catch up."  In the long run, the frequency of execution will be
116
     * exactly the reciprocal of the specified period (assuming the system
117
     * clock underlying <tt>Object.wait(long)</tt> is accurate).
118
     *
119
     * <p>Fixed-rate execution is appropriate for recurring activities that
120
     * are sensitive to <i>absolute</i> time, such as ringing a chime every
121
     * hour on the hour, or running scheduled maintenance every day at a
122
     * particular time.  It is also appropriate for recurring activities
123
     * where the total time to perform a fixed number of executions is
124
     * important, such as a countdown timer that ticks once every second for
125
     * ten seconds.  Finally, fixed-rate execution is appropriate for
126
     * scheduling multiple repeating timer tasks that must remain synchronized
127
     * with respect to one another.
128
     *
129
     * @param task   task to be scheduled.
130
     * @param delay  delay in milliseconds before task is to be executed.
131
     * @param period time in milliseconds between successive task executions.
132
     * @throws IllegalArgumentException if {@code delay < 0}, or
133
     *         {@code delay + System.currentTimeMillis() < 0}, or
134
     *         {@code period <= 0}
135
     * @throws IllegalStateException if task was already scheduled or
136
     *         cancelled, timer was cancelled, or timer thread terminated.
137
     * @throws NullPointerException if {@code task} is null
138
     */
139
    public function scheduleAtFixedRate(TimerTask $task, $delayOrTime, int $period): void
140
    {
141
        if (is_int($delayOrTime)) {
142
            if ($delayOrTime < 0) {
143
                throw new \Exception("Negative delay.");
144
            }
145
            if ($period <= 0) {
146
                throw new \Exception("Non-positive period.");
147
            }
148
            $this->sched($task, floor(microtime(true) * 1000) + $delayOrTime, $period);
0 ignored issues
show
Bug introduced by
floor(microtime(true) * 1000) + $delayOrTime of type double is incompatible with the type integer expected by parameter $time of Jabe\Engine\Impl\Util\Timer\Timer::sched(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
            $this->sched($task, /** @scrutinizer ignore-type */ floor(microtime(true) * 1000) + $delayOrTime, $period);
Loading history...
149
        } elseif ($delayOrTime instanceof \DateTime) {
150
            if ($period <= 0) {
151
                throw new \Exception("Non-positive period.");
152
            }
153
            $this->sched($task, $delayOrDatetime->getTimestamp() * 1000, $period);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $delayOrDatetime does not exist. Did you maybe mean $delayOrTime?
Loading history...
154
        }
155
    }
156
157
    /**
158
     * Schedule the specified timer task for execution at the specified
159
     * time with the specified period, in milliseconds.  If period is
160
     * positive, the task is scheduled for repeated execution; if period is
161
     * zero, the task is scheduled for one-time execution. Time is specified
162
     * in Date.getTime() format.  This method checks timer state, task state,
163
     * and initial execution time, but not period.
164
     *
165
     * @throws IllegalArgumentException if <tt>time</tt> is negative.
166
     * @throws IllegalStateException if task was already scheduled or
167
     *         cancelled, timer was cancelled, or timer thread terminated.
168
     * @throws NullPointerException if {@code task} is null
169
     */
170
    private function sched(TimerTask $task, int $time, int $period): void
171
    {
172
        if ($time < 0) {
173
            throw new \Exception("Illegal execution time.");
174
        }
175
176
        // Constrain value of period sufficiently to prevent numeric
177
        // overflow while still being effectively infinitely large.
178
        if (abs($period) > (PHP_INT_MAX >> 1)) {
179
            $period >>= 1;
180
        }
181
182
        $this->queue->synchronized(function ($scope, $task, $period) {
183
            if (!$scope->thread->newTasksMayBeScheduled) {
184
                throw new \Exception("Timer already cancelled.");
185
            }
186
187
            $task->lock->synchronized(function ($task, $period) {
188
                if ($task->state != TimerTask::VIRGIN) {
189
                    throw new \Exception("Task already scheduled or cancelled");
190
                }
191
                $task->nextExecutionTime = $time;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $time seems to be never defined.
Loading history...
192
                $task->period = $period;
193
                $task->state = TimerTask::SCHEDULED;
194
            }, $task, $period);
195
196
            $scope->queue->add($task);
197
            if ($scope->queue->getMin() == $task) {
198
                $scope->queue->notify();
199
            }
200
        }, $this, $task, $period);
201
    }
202
203
    /**
204
     * Terminates this timer, discarding any currently scheduled tasks.
205
     * Does not interfere with a currently executing task (if it exists).
206
     * Once a timer has been terminated, its execution thread terminates
207
     * gracefully, and no more tasks may be scheduled on it.
208
     *
209
     * <p>Note that calling this method from within the run method of a
210
     * timer task that was invoked by this timer absolutely guarantees that
211
     * the ongoing task execution is the last task execution that will ever
212
     * be performed by this timer.
213
     *
214
     * <p>This method may be called repeatedly; the second and subsequent
215
     * calls have no effect.
216
     */
217
    public function cancel(): void
218
    {
219
        $this->queue->synchronized(function ($scope) {
220
            $scope->thread->newTasksMayBeScheduled = false;
221
            $scope->queue->clear();
222
            $scope->queue->notify();  // In case queue was already empty.
223
        }, $this);
224
    }
225
226
    /**
227
     * Removes all cancelled tasks from this timer's task queue.  <i>Calling
228
     * this method has no effect on the behavior of the timer</i>, but
229
     * eliminates the references to the cancelled tasks from the queue.
230
     * If there are no external references to these tasks, they become
231
     * eligible for garbage collection.
232
     *
233
     * <p>Most programs will have no need to call this method.
234
     * It is designed for use by the rare application that cancels a large
235
     * number of tasks.  Calling this method trades time for space: the
236
     * runtime of the method may be proportional to n + c log n, where n
237
     * is the number of tasks in the queue and c is the number of cancelled
238
     * tasks.
239
     *
240
     * <p>Note that it is permissible to call this method from within a
241
     * a task scheduled on this timer.
242
     *
243
     * @return the number of tasks removed from the queue.
244
     * @since 1.5
245
     */
246
    public function purge(): int
247
    {
248
        $result = 0;
249
250
        $this->queue->synchronized(function ($scope, $result) {
251
            for ($i = $scope->queue->size(); $i > 0; $i -= 1) {
252
                if ($scope->queue->get($i)->state == TimerTask::CANCELLED) {
253
                    $scope->queue->quickRemove($i);
254
                    $result += 1;
255
                }
256
            }
257
258
            if ($result != 0) {
259
                $scope->queue->heapify();
260
            }
261
        }, $this, $result);
262
263
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result returns the type integer which is incompatible with the documented return type Jabe\Engine\Impl\Util\Timer\the.
Loading history...
264
    }
265
}
266