Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.97% |
32 / 33 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
CriticalSectionProvider | |
96.97% |
32 / 33 |
|
80.00% |
4 / 5 |
7 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
enter | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
exit | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
scopedEnter | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
getEmergencyLimit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace Wikimedia\RequestTimeout; |
4 | |
5 | /** |
6 | * A class providing a named critical section concept. When the code is inside |
7 | * a critical section, if a request timeout occurs, it is queued and then |
8 | * delivered when the critical section exits. |
9 | * |
10 | * The class stores configuration for "emergency timeouts". This is a second |
11 | * timeout which limits the amount of time a critical section may be open. |
12 | */ |
13 | class CriticalSectionProvider { |
14 | /** @var RequestTimeout */ |
15 | private $requestTimeout; |
16 | |
17 | /** @var float */ |
18 | private $emergencyLimit; |
19 | |
20 | /** @var callable|null */ |
21 | private $emergencyCallback; |
22 | |
23 | /** @var callable|null */ |
24 | private $implicitExitCallback; |
25 | |
26 | /** @var array */ |
27 | private $stack = []; |
28 | |
29 | /** |
30 | * @internal Use RequestTimeout::createCriticalSectionProvider() |
31 | * |
32 | * @param RequestTimeout $requestTimeout The parent object |
33 | * @param float $emergencyLimit The emergency timeout in seconds |
34 | * @param callable|null $emergencyCallback A callback to call when the |
35 | * emergency timeout expires. If null, an exception will be thrown. |
36 | * @param callable|null $implicitExitCallback A callback to call when a scoped |
37 | * critical section is exited implicitly by scope destruction, rather than |
38 | * by CriticalSectionScope::exit(). |
39 | */ |
40 | public function __construct( |
41 | RequestTimeout $requestTimeout, |
42 | $emergencyLimit, |
43 | $emergencyCallback, |
44 | $implicitExitCallback |
45 | ) { |
46 | $this->requestTimeout = $requestTimeout; |
47 | $this->emergencyLimit = $emergencyLimit; |
48 | $this->emergencyCallback = $emergencyCallback; |
49 | $this->implicitExitCallback = $implicitExitCallback; |
50 | } |
51 | |
52 | /** |
53 | * Enter a critical section, giving it a name. The name should uniquely |
54 | * identify the calling code. |
55 | * |
56 | * Multiple critical sections may be active at a given time. Critical |
57 | * sections created by this method must be exited in the reverse order to |
58 | * which they were created, i.e. there is a stack of named critical |
59 | * sections. |
60 | * |
61 | * @param string $name |
62 | * @param float|null $emergencyLimit If non-null, this will override the |
63 | * configured emergency timeout |
64 | * @param callable|null $emergencyCallback If non-null, this will override |
65 | * the configured emergency timeout callback. |
66 | */ |
67 | public function enter( $name, $emergencyLimit = null, $emergencyCallback = null ) { |
68 | $id = $this->requestTimeout->enterCriticalSection( |
69 | $name, |
70 | $emergencyLimit ?? $this->emergencyLimit, |
71 | $emergencyCallback ?? $this->emergencyCallback |
72 | ); |
73 | $this->stack[ count( $this->stack ) ] = [ |
74 | 'name' => $name, |
75 | 'id' => $id |
76 | ]; |
77 | } |
78 | |
79 | /** |
80 | * Exit a named critical section. If the name does not match the most recent |
81 | * call to enter(), an exception will be thrown. |
82 | * |
83 | * @throws CriticalSectionMismatchException |
84 | * @throws TimeoutException |
85 | * @param string $name |
86 | */ |
87 | public function exit( $name ) { |
88 | $i = count( $this->stack ) - 1; |
89 | if ( $i === -1 ) { |
90 | throw new CriticalSectionMismatchException( $name, '[none]' ); |
91 | } |
92 | if ( $this->stack[$i]['name'] !== $name ) { |
93 | throw new CriticalSectionMismatchException( $name, $this->stack[$i]['name'] ); |
94 | } |
95 | $this->requestTimeout->exitCriticalSection( $this->stack[$i]['id'] ); |
96 | unset( $this->stack[$i] ); |
97 | } |
98 | |
99 | /** |
100 | * Enter a critical section, and return a scope variable. The critical |
101 | * section will automatically exit when the scope variable is destroyed. |
102 | * |
103 | * Multiple critical sections may be active at a given time. There is no |
104 | * restriction on the order in which critical sections created by this |
105 | * method are exited. |
106 | * |
107 | * NOTE: Callers should typically call CriticalSectionScope::exit() instead |
108 | * of waiting for __destruct() to be called, since exiting a critical |
109 | * section may throw a timeout exception, but it is a fatal error to throw |
110 | * an exception from a destructor during request shutdown. |
111 | * |
112 | * @param string $name A name for the critical section, used in error messages |
113 | * @param float|null $emergencyLimit If non-null, this will override the |
114 | * configured emergency timeout |
115 | * @param callable|null $emergencyCallback If non-null, this will override |
116 | * the configured emergency timeout callback. |
117 | * @param callable|null $implicitExitCallback If non-null, this will override |
118 | * the configured implicit exit callback. The callback will be called if the |
119 | * section is exited in __destruct() instead of by calling exit(). |
120 | * @return CriticalSectionScope |
121 | */ |
122 | public function scopedEnter( $name, $emergencyLimit = null, |
123 | $emergencyCallback = null, $implicitExitCallback = null |
124 | ) { |
125 | $id = $this->requestTimeout->enterCriticalSection( |
126 | $name, |
127 | $emergencyLimit ?? $this->emergencyLimit, |
128 | $emergencyCallback ?? $this->emergencyCallback |
129 | ); |
130 | |
131 | return new CriticalSectionScope( |
132 | $id, |
133 | function ( $id ) { |
134 | $this->requestTimeout->exitCriticalSection( $id ); |
135 | }, |
136 | $implicitExitCallback ?? $this->implicitExitCallback |
137 | ); |
138 | } |
139 | |
140 | /** |
141 | * Get the configured emergency time limit |
142 | * |
143 | * @since 1.1.0 |
144 | * @return float |
145 | */ |
146 | public function getEmergencyLimit() { |
147 | return $this->emergencyLimit; |
148 | } |
149 | } |