66use Evenement \EventEmitter ;
77use React \MySQL \Exception ;
88use React \MySQL \Factory ;
9+ use React \EventLoop \LoopInterface ;
10+ use React \MySQL \QueryResult ;
911
1012/**
1113 * @internal
@@ -19,37 +21,94 @@ class LazyConnection extends EventEmitter implements ConnectionInterface
1921 private $ closed = false ;
2022 private $ busy = false ;
2123
22- public function __construct (Factory $ factory , $ uri )
24+ /**
25+ * @var ConnectionInterface|null
26+ */
27+ private $ disconnecting ;
28+
29+ private $ loop ;
30+ private $ idlePeriod = 60.0 ;
31+ private $ idleTimer ;
32+ private $ pending = 0 ;
33+
34+ public function __construct (Factory $ factory , $ uri , LoopInterface $ loop )
2335 {
36+ $ args = array ();
37+ \parse_str (\parse_url ($ uri , \PHP_URL_QUERY ), $ args );
38+ if (isset ($ args ['idle ' ])) {
39+ $ this ->idlePeriod = (float )$ args ['idle ' ];
40+ }
41+
2442 $ this ->factory = $ factory ;
2543 $ this ->uri = $ uri ;
44+ $ this ->loop = $ loop ;
2645 }
2746
2847 private function connecting ()
2948 {
30- if ($ this ->connecting === null ) {
31- $ this ->connecting = $ this ->factory ->createConnection ($ this ->uri );
49+ if ($ this ->connecting !== null ) {
50+ return $ this ->connecting ;
51+ }
3252
33- $ this ->connecting ->then (function (ConnectionInterface $ connection ) {
34- // connection completed => forward error and close events
35- $ connection ->on ('error ' , function ($ e ) {
36- $ this ->emit ('error ' , [$ e ]);
37- });
38- $ connection ->on ('close ' , function () {
39- $ this ->close ();
40- });
41- }, function (\Exception $ e ) {
42- // connection failed => emit error if connection is not already closed
43- if ($ this ->closed ) {
44- return ;
45- }
53+ // force-close connection if still waiting for previous disconnection
54+ if ($ this ->disconnecting !== null ) {
55+ $ this ->disconnecting ->close ();
56+ $ this ->disconnecting = null ;
57+ }
4658
47- $ this ->emit ('error ' , [$ e ]);
48- $ this ->close ();
59+ $ this ->connecting = $ connecting = $ this ->factory ->createConnection ($ this ->uri );
60+ $ this ->connecting ->then (function (ConnectionInterface $ connection ) {
61+ // connection completed => remember only until closed
62+ $ connection ->on ('close ' , function () {
63+ $ this ->connecting = null ;
64+
65+ if ($ this ->idleTimer !== null ) {
66+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
67+ $ this ->idleTimer = null ;
68+ }
4969 });
70+ }, function () {
71+ // connection failed => discard connection attempt
72+ $ this ->connecting = null ;
73+ });
74+
75+ return $ connecting ;
76+ }
77+
78+ private function awake ()
79+ {
80+ ++$ this ->pending ;
81+
82+ if ($ this ->idleTimer !== null ) {
83+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
84+ $ this ->idleTimer = null ;
5085 }
86+ }
5187
52- return $ this ->connecting ;
88+ private function idle ()
89+ {
90+ --$ this ->pending ;
91+
92+ if ($ this ->pending < 1 && $ this ->idlePeriod >= 0 ) {
93+ $ this ->idleTimer = $ this ->loop ->addTimer ($ this ->idlePeriod , function () {
94+ $ this ->connecting ->then (function (ConnectionInterface $ connection ) {
95+ $ this ->disconnecting = $ connection ;
96+ $ connection ->quit ()->then (
97+ function () {
98+ // successfully disconnected => remove reference
99+ $ this ->disconnecting = null ;
100+ },
101+ function () use ($ connection ) {
102+ // soft-close failed => force-close connection
103+ $ connection ->close ();
104+ $ this ->disconnecting = null ;
105+ }
106+ );
107+ });
108+ $ this ->connecting = null ;
109+ $ this ->idleTimer = null ;
110+ });
111+ }
53112 }
54113
55114 public function query ($ sql , array $ params = [])
@@ -59,7 +118,17 @@ public function query($sql, array $params = [])
59118 }
60119
61120 return $ this ->connecting ()->then (function (ConnectionInterface $ connection ) use ($ sql , $ params ) {
62- return $ connection ->query ($ sql , $ params );
121+ $ this ->awake ();
122+ return $ connection ->query ($ sql , $ params )->then (
123+ function (QueryResult $ result ) {
124+ $ this ->idle ();
125+ return $ result ;
126+ },
127+ function (\Exception $ e ) {
128+ $ this ->idle ();
129+ throw $ e ;
130+ }
131+ );
63132 });
64133 }
65134
@@ -71,7 +140,14 @@ public function queryStream($sql, $params = [])
71140
72141 return \React \Promise \Stream \unwrapReadable (
73142 $ this ->connecting ()->then (function (ConnectionInterface $ connection ) use ($ sql , $ params ) {
74- return $ connection ->queryStream ($ sql , $ params );
143+ $ stream = $ connection ->queryStream ($ sql , $ params );
144+
145+ $ this ->awake ();
146+ $ stream ->on ('close ' , function () {
147+ $ this ->idle ();
148+ });
149+
150+ return $ stream ;
75151 })
76152 );
77153 }
@@ -83,7 +159,16 @@ public function ping()
83159 }
84160
85161 return $ this ->connecting ()->then (function (ConnectionInterface $ connection ) {
86- return $ connection ->ping ();
162+ $ this ->awake ();
163+ return $ connection ->ping ()->then (
164+ function () {
165+ $ this ->idle ();
166+ },
167+ function (\Exception $ e ) {
168+ $ this ->idle ();
169+ throw $ e ;
170+ }
171+ );
87172 });
88173 }
89174
@@ -100,7 +185,16 @@ public function quit()
100185 }
101186
102187 return $ this ->connecting ()->then (function (ConnectionInterface $ connection ) {
103- return $ connection ->quit ();
188+ $ this ->awake ();
189+ return $ connection ->quit ()->then (
190+ function () {
191+ $ this ->close ();
192+ },
193+ function (\Exception $ e ) {
194+ $ this ->close ();
195+ throw $ e ;
196+ }
197+ );
104198 });
105199 }
106200
@@ -112,6 +206,12 @@ public function close()
112206
113207 $ this ->closed = true ;
114208
209+ // force-close connection if still waiting for previous disconnection
210+ if ($ this ->disconnecting !== null ) {
211+ $ this ->disconnecting ->close ();
212+ $ this ->disconnecting = null ;
213+ }
214+
115215 // either close active connection or cancel pending connection attempt
116216 if ($ this ->connecting !== null ) {
117217 $ this ->connecting ->then (function (ConnectionInterface $ connection ) {
@@ -121,6 +221,11 @@ public function close()
121221 $ this ->connecting = null ;
122222 }
123223
224+ if ($ this ->idleTimer !== null ) {
225+ $ this ->loop ->cancelTimer ($ this ->idleTimer );
226+ $ this ->idleTimer = null ;
227+ }
228+
124229 $ this ->emit ('close ' );
125230 $ this ->removeAllListeners ();
126231 }
0 commit comments