33namespace React \Mysql \Io ;
44
55use Evenement \EventEmitter ;
6+ use React \EventLoop \LoopInterface ;
67use React \Mysql \Commands \CommandInterface ;
78use React \Mysql \Commands \PingCommand ;
89use React \Mysql \Commands \QueryCommand ;
@@ -29,30 +30,66 @@ class Connection extends EventEmitter
2930 private $ executor ;
3031
3132 /**
32- * @var integer
33+ * @var int one of the state constants (may change, but should be used readonly from outside)
34+ * @see self::STATE_*
3335 */
34- private $ state = self ::STATE_AUTHENTICATED ;
36+ public $ state = self ::STATE_AUTHENTICATED ;
3537
3638 /**
3739 * @var SocketConnectionInterface
3840 */
3941 private $ stream ;
4042
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+
4158 /**
4259 * Connection constructor.
4360 *
4461 * @param SocketConnectionInterface $stream
4562 * @param Executor $executor
63+ * @param Parser $parser
64+ * @param LoopInterface $loop
65+ * @param ?float $idlePeriod
4666 */
47- public function __construct (SocketConnectionInterface $ stream , Executor $ executor )
67+ public function __construct (SocketConnectionInterface $ stream , Executor $ executor, Parser $ parser , LoopInterface $ loop , $ idlePeriod )
4868 {
4969 $ this ->stream = $ stream ;
5070 $ this ->executor = $ executor ;
71+ $ this ->parser = $ parser ;
72+
73+ $ this ->loop = $ loop ;
74+ if ($ idlePeriod !== null ) {
75+ $ this ->idlePeriod = $ idlePeriod ;
76+ }
5177
5278 $ stream ->on ('error ' , [$ this , 'handleConnectionError ' ]);
5379 $ stream ->on ('close ' , [$ this , 'handleConnectionClosed ' ]);
5480 }
5581
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+
5693 /**
5794 * {@inheritdoc}
5895 */
@@ -71,6 +108,7 @@ public function query($sql, array $params = [])
71108 return \React \Promise \reject ($ e );
72109 }
73110
111+ $ this ->awake ();
74112 $ deferred = new Deferred ();
75113
76114 // store all result set rows until result set end
@@ -86,11 +124,13 @@ public function query($sql, array $params = [])
86124
87125 $ rows = [];
88126
127+ $ this ->idle ();
89128 $ deferred ->resolve ($ result );
90129 });
91130
92131 // resolve / reject status reply (response without result set)
93132 $ command ->on ('error ' , function ($ error ) use ($ deferred ) {
133+ $ this ->idle ();
94134 $ deferred ->reject ($ error );
95135 });
96136 $ command ->on ('success ' , function () use ($ command , $ deferred ) {
@@ -99,6 +139,7 @@ public function query($sql, array $params = [])
99139 $ result ->insertId = $ command ->insertId ;
100140 $ result ->warningCount = $ command ->warningCount ;
101141
142+ $ this ->idle ();
102143 $ deferred ->resolve ($ result );
103144 });
104145
@@ -115,20 +156,30 @@ public function queryStream($sql, $params = [])
115156 $ command = new QueryCommand ();
116157 $ command ->setQuery ($ query );
117158 $ this ->_doCommand ($ command );
159+ $ this ->awake ();
160+
161+ $ stream = new QueryStream ($ command , $ this ->stream );
162+ $ stream ->on ('close ' , function () {
163+ $ this ->idle ();
164+ });
118165
119- return new QueryStream ( $ command , $ this -> stream ) ;
166+ return $ stream ;
120167 }
121168
122169 public function ping ()
123170 {
124171 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+ });
132183 });
133184 }
134185
@@ -137,6 +188,10 @@ public function quit()
137188 return new Promise (function ($ resolve , $ reject ) {
138189 $ command = $ this ->_doCommand (new QuitCommand ());
139190 $ this ->state = self ::STATE_CLOSING ;
191+
192+ // mark connection as "awake" until it is closed, so never "idle"
193+ $ this ->awake ();
194+
140195 $ command ->on ('success ' , function () use ($ resolve ) {
141196 $ resolve (null );
142197 $ this ->close ();
@@ -158,6 +213,11 @@ public function close()
158213 $ remoteClosed = $ this ->stream ->isReadable () === false && $ this ->stream ->isWritable () === false ;
159214 $ this ->stream ->close ();
160215
216+ if ($ this ->idleTimer !== null ) {
217+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
218+ $ this ->idleTimer = null ;
219+ }
220+
161221 // reject all pending commands if connection is closed
162222 while (!$ this ->executor ->isIdle ()) {
163223 $ command = $ this ->executor ->dequeue ();
@@ -223,4 +283,29 @@ protected function _doCommand(CommandInterface $command)
223283
224284 return $ this ->executor ->enqueue ($ command );
225285 }
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+ }
226311}
0 commit comments