3
3
namespace React \Mysql \Io ;
4
4
5
5
use Evenement \EventEmitter ;
6
+ use React \EventLoop \LoopInterface ;
6
7
use React \Mysql \Commands \CommandInterface ;
7
8
use React \Mysql \Commands \PingCommand ;
8
9
use React \Mysql \Commands \QueryCommand ;
@@ -29,30 +30,66 @@ class Connection extends EventEmitter
29
30
private $ executor ;
30
31
31
32
/**
32
- * @var integer
33
+ * @var int one of the state constants (may change, but should be used readonly from outside)
34
+ * @see self::STATE_*
33
35
*/
34
- private $ state = self ::STATE_AUTHENTICATED ;
36
+ public $ state = self ::STATE_AUTHENTICATED ;
35
37
36
38
/**
37
39
* @var SocketConnectionInterface
38
40
*/
39
41
private $ stream ;
40
42
43
+ /** @var Parser */
44
+ private $ parser ;
45
+
46
+ /** @var LoopInterface */
47
+ private $ loop ;
48
+
49
+ /** @var float */
50
+ private $ idlePeriod = 0.001 ;
51
+
52
+ /** @var ?\React\EventLoop\TimerInterface */
53
+ private $ idleTimer ;
54
+
55
+ /** @var int */
56
+ private $ pending = 0 ;
57
+
41
58
/**
42
59
* Connection constructor.
43
60
*
44
61
* @param SocketConnectionInterface $stream
45
62
* @param Executor $executor
63
+ * @param Parser $parser
64
+ * @param LoopInterface $loop
65
+ * @param ?float $idlePeriod
46
66
*/
47
- public function __construct (SocketConnectionInterface $ stream , Executor $ executor )
67
+ public function __construct (SocketConnectionInterface $ stream , Executor $ executor, Parser $ parser , LoopInterface $ loop , $ idlePeriod )
48
68
{
49
69
$ this ->stream = $ stream ;
50
70
$ this ->executor = $ executor ;
71
+ $ this ->parser = $ parser ;
72
+
73
+ $ this ->loop = $ loop ;
74
+ if ($ idlePeriod !== null ) {
75
+ $ this ->idlePeriod = $ idlePeriod ;
76
+ }
51
77
52
78
$ stream ->on ('error ' , [$ this , 'handleConnectionError ' ]);
53
79
$ stream ->on ('close ' , [$ this , 'handleConnectionClosed ' ]);
54
80
}
55
81
82
+ /**
83
+ * busy executing some command such as query or ping
84
+ *
85
+ * @return bool
86
+ * @throws void
87
+ */
88
+ public function isBusy ()
89
+ {
90
+ return $ this ->parser ->isBusy () || !$ this ->executor ->isIdle ();
91
+ }
92
+
56
93
/**
57
94
* {@inheritdoc}
58
95
*/
@@ -71,6 +108,7 @@ public function query($sql, array $params = [])
71
108
return \React \Promise \reject ($ e );
72
109
}
73
110
111
+ $ this ->awake ();
74
112
$ deferred = new Deferred ();
75
113
76
114
// store all result set rows until result set end
@@ -86,11 +124,13 @@ public function query($sql, array $params = [])
86
124
87
125
$ rows = [];
88
126
127
+ $ this ->idle ();
89
128
$ deferred ->resolve ($ result );
90
129
});
91
130
92
131
// resolve / reject status reply (response without result set)
93
132
$ command ->on ('error ' , function ($ error ) use ($ deferred ) {
133
+ $ this ->idle ();
94
134
$ deferred ->reject ($ error );
95
135
});
96
136
$ command ->on ('success ' , function () use ($ command , $ deferred ) {
@@ -99,6 +139,7 @@ public function query($sql, array $params = [])
99
139
$ result ->insertId = $ command ->insertId ;
100
140
$ result ->warningCount = $ command ->warningCount ;
101
141
142
+ $ this ->idle ();
102
143
$ deferred ->resolve ($ result );
103
144
});
104
145
@@ -115,20 +156,30 @@ public function queryStream($sql, $params = [])
115
156
$ command = new QueryCommand ();
116
157
$ command ->setQuery ($ query );
117
158
$ this ->_doCommand ($ command );
159
+ $ this ->awake ();
160
+
161
+ $ stream = new QueryStream ($ command , $ this ->stream );
162
+ $ stream ->on ('close ' , function () {
163
+ $ this ->idle ();
164
+ });
118
165
119
- return new QueryStream ( $ command , $ this -> stream ) ;
166
+ return $ stream ;
120
167
}
121
168
122
169
public function ping ()
123
170
{
124
171
return new Promise (function ($ resolve , $ reject ) {
125
- $ this ->_doCommand (new PingCommand ())
126
- ->on ('error ' , function ($ reason ) use ($ reject ) {
127
- $ reject ($ reason );
128
- })
129
- ->on ('success ' , function () use ($ resolve ) {
130
- $ resolve (null );
131
- });
172
+ $ command = $ this ->_doCommand (new PingCommand ());
173
+ $ this ->awake ();
174
+
175
+ $ command ->on ('success ' , function () use ($ resolve ) {
176
+ $ this ->idle ();
177
+ $ resolve (null );
178
+ });
179
+ $ command ->on ('error ' , function ($ reason ) use ($ reject ) {
180
+ $ this ->idle ();
181
+ $ reject ($ reason );
182
+ });
132
183
});
133
184
}
134
185
@@ -137,6 +188,10 @@ public function quit()
137
188
return new Promise (function ($ resolve , $ reject ) {
138
189
$ command = $ this ->_doCommand (new QuitCommand ());
139
190
$ this ->state = self ::STATE_CLOSING ;
191
+
192
+ // mark connection as "awake" until it is closed, so never "idle"
193
+ $ this ->awake ();
194
+
140
195
$ command ->on ('success ' , function () use ($ resolve ) {
141
196
$ resolve (null );
142
197
$ this ->close ();
@@ -158,6 +213,11 @@ public function close()
158
213
$ remoteClosed = $ this ->stream ->isReadable () === false && $ this ->stream ->isWritable () === false ;
159
214
$ this ->stream ->close ();
160
215
216
+ if ($ this ->idleTimer !== null ) {
217
+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
218
+ $ this ->idleTimer = null ;
219
+ }
220
+
161
221
// reject all pending commands if connection is closed
162
222
while (!$ this ->executor ->isIdle ()) {
163
223
$ command = $ this ->executor ->dequeue ();
@@ -223,4 +283,29 @@ protected function _doCommand(CommandInterface $command)
223
283
224
284
return $ this ->executor ->enqueue ($ command );
225
285
}
286
+
287
+ private function awake ()
288
+ {
289
+ ++$ this ->pending ;
290
+
291
+ if ($ this ->idleTimer !== null ) {
292
+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
293
+ $ this ->idleTimer = null ;
294
+ }
295
+ }
296
+
297
+ private function idle ()
298
+ {
299
+ --$ this ->pending ;
300
+
301
+ if ($ this ->pending < 1 && $ this ->idlePeriod >= 0 && $ this ->state === self ::STATE_AUTHENTICATED ) {
302
+ $ this ->idleTimer = $ this ->loop ->addTimer ($ this ->idlePeriod , function () {
303
+ // soft-close connection and emit close event afterwards both on success or on error
304
+ $ this ->idleTimer = null ;
305
+ $ this ->quit ()->then (null , function () {
306
+ // ignore to avoid reporting unhandled rejection
307
+ });
308
+ });
309
+ }
310
+ }
226
311
}
0 commit comments