@@ -141,20 +141,48 @@ def __init__(self, connection_settings, server_id,
141
141
fail_on_table_metadata_unavailable = False ,
142
142
slave_heartbeat = None ):
143
143
"""
144
- Attributes:
144
+ Parameters:
145
+ connection_settings: a dict of parameters passed to `pymysql.connect`
146
+ or `pymysql_wrapper`, of which "db" parameter is not necessary
147
+ pymysql_wrapper: custom replacement for `pymysql.connect`
145
148
ctl_connection_settings: Connection settings for cluster holding
146
- schema information
147
- resume_stream: Start for event from position or the latest event of
148
- binlog or from older available event
149
+ schema information, which could be None, in which case
150
+ `connection_settings` will be used as ctl_connection_settings,
151
+ except for that "db" will be replaced to "information_schema"
152
+ resume_stream: True or False. control the start point of the returned
153
+ events, only works when `auto_position` is None.
154
+ `fetchone` will fetch data from:
155
+ 1.the begining of `log_file`: if `resume_stream` is False
156
+ 2.`log_pos` of `log_file`: if resume_stream is True, and it's
157
+ the first time to fetch the data
158
+ 3.the event right next to the last fetched event: when resume_stream
159
+ is True and it's not the first time to fetch data
160
+ note: the log position will be set back to the begging of `log_file`
161
+ each time the client is disconnected and then reconnected
162
+ to the mysql server (OperationalError 2006/2013) if resume_stream
163
+ is False. so it's suggested to set resume_stream to True.
164
+
149
165
blocking: When master has finished reading/sending binlog it will
150
166
send EOF instead of blocking connection.
151
167
only_events: Array of allowed events
152
168
ignored_events: Array of ignored events
153
- log_file: Set replication start log file
169
+ log_file: Set replication start log file. if ether `log_file` or
170
+ `log_pos` is None, and auto_position is None, then log_pos
171
+ and log_file will be set as the values returned by the query
172
+ "SHOW MASTER STATUS"
154
173
log_pos: Set replication start log pos (resume_stream should be
155
- true)
174
+ true). if ether `log_file` or `log_pos` is None, and auto_position
175
+ is None, then log_pos and log_file will be set as the values
176
+ returned by the query "SHOW MASTER STATUS", and log_pos will
177
+ be set as 4 (the start position of any log file) if resume_stream
178
+ is a false value
156
179
end_log_pos: Set replication end log pos
157
- auto_position: Use master_auto_position gtid to set position
180
+ auto_position: a string of replicated GTIDs. all the events except
181
+ for thoses included in `auto_position` and those purged by
182
+ the source server will be sent to the client. a valid `auto_position`
183
+ looks like:
184
+ 19d69c1e-ae97-4b8c-a1ef-9e12ba966457:1-3:8-10,
185
+ 1c2aad49-ae92-409a-b4df-d05a03e4702e:42-47:80-100:130-140
158
186
only_tables: An array with the tables you want to watch (only works
159
187
in binlog_format ROW)
160
188
ignored_tables: An array with the tables you want to skip
@@ -174,13 +202,22 @@ def __init__(self, connection_settings, server_id,
174
202
many event to skip in binlog). See
175
203
MASTER_HEARTBEAT_PERIOD in mysql documentation
176
204
for semantics
205
+
206
+ Notes:
207
+ the log position will be set back to the begging of `log_file`
208
+ each time the client is disconnected and then auto-reconnected
209
+ to the mysql server (OperationalError 2006/2013) if resume_stream
210
+ is False. so it's suggested to set resume_stream to True.
211
+
212
+ an additional RotateEvent and FormatDescriptionEvent will be
213
+ fetched each time the client is disconnected and then auto-
214
+ reconnected to the server. (no matter resume_stream is True
215
+ or False)
177
216
"""
178
217
179
218
self .__connection_settings = connection_settings
180
219
self .__connection_settings .setdefault ("charset" , "utf8" )
181
220
182
- self .__connected_stream = False
183
- self .__connected_ctl = False
184
221
self .__resume_stream = resume_stream
185
222
self .__blocking = blocking
186
223
self ._ctl_connection_settings = ctl_connection_settings
@@ -226,24 +263,26 @@ def __init__(self, connection_settings, server_id,
226
263
self .pymysql_wrapper = pymysql .connect
227
264
228
265
def close (self ):
229
- if self . __connected_stream :
266
+ if getattr ( self , '_stream_connection' , None ) and self . _stream_connection . open :
230
267
self ._stream_connection .close ()
231
- self .__connected_stream = False
232
- if self .__connected_ctl :
268
+ if getattr (self , '_ctl_connection' , None ):
233
269
# break reference cycle between stream reader and underlying
234
270
# mysql connection object
235
271
self ._ctl_connection ._get_table_information = None
236
- self ._ctl_connection .close ()
237
- self . __connected_ctl = False
272
+ if self ._ctl_connection .open :
273
+ self . _ctl_connection . close ()
238
274
239
- def __connect_to_ctl (self ):
275
+ def __connect_to_ctl (self , force_reconnect = False ):
276
+ if self .__connected_ctl :
277
+ if not force_reconnect :
278
+ return
279
+ self ._ctl_connection .close ()
240
280
if not self ._ctl_connection_settings :
241
281
self ._ctl_connection_settings = dict (self .__connection_settings )
242
282
self ._ctl_connection_settings ["db" ] = "information_schema"
243
283
self ._ctl_connection_settings ["cursorclass" ] = DictCursor
244
284
self ._ctl_connection = self .pymysql_wrapper (** self ._ctl_connection_settings )
245
285
self ._ctl_connection ._get_table_information = self .__get_table_information
246
- self .__connected_ctl = True
247
286
248
287
def __checksum_enabled (self ):
249
288
"""Return True if binlog-checksum = CRC32. Only for MySQL > 5.6"""
@@ -274,12 +313,29 @@ def _register_slave(self):
274
313
self ._stream_connection ._next_seq_id = 1
275
314
self ._stream_connection ._read_packet ()
276
315
277
- def __connect_to_stream (self ):
316
+ @property
317
+ def __connected_stream (self ):
318
+ return bool (getattr (self , '_stream_connection' , None ) and \
319
+ self ._stream_connection .open )
320
+
321
+ @property
322
+ def __connected_ctl (self ):
323
+ return bool (getattr (self , '_ctl_connection' , None ) and \
324
+ self ._ctl_connection .open )
325
+
326
+ def __connect_to_stream (self , force_reconnect = False ):
327
+ if self .__connected_stream :
328
+ if not force_reconnect :
329
+ return
330
+ self ._stream_connection .close ()
331
+
278
332
# log_pos (4) -- position in the binlog-file to start the stream with
279
333
# flags (2) BINLOG_DUMP_NON_BLOCK (0 or 1)
280
334
# server_id (4) -- server id of this slave
281
335
# log_file (string.EOF) -- filename of the binlog on the master
282
336
self ._stream_connection = self .pymysql_wrapper (** self .__connection_settings )
337
+ if pymysql .__version__ < LooseVersion ("0.6" ):
338
+ self ._stream_connection ._read_packet = self ._stream_connection .read_packet
283
339
284
340
self .__use_checksum = self .__checksum_enabled ()
285
341
@@ -301,9 +357,7 @@ def __connect_to_stream(self):
301
357
4294967 ))
302
358
# If heartbeat is too low, the connection will disconnect before,
303
359
# this is also the behavior in mysql
304
- heartbeat = float (min (net_timeout / 2. , self .slave_heartbeat ))
305
- if heartbeat > 4294967 :
306
- heartbeat = 4294967
360
+ heartbeat = float (min (net_timeout / 2. , self .slave_heartbeat , 4294967 ))
307
361
308
362
# master_heartbeat_period is nanoseconds
309
363
heartbeat = int (heartbeat * 1000000000 )
@@ -419,29 +473,27 @@ def __connect_to_stream(self):
419
473
else :
420
474
self ._stream_connection ._write_bytes (prelude )
421
475
self ._stream_connection ._next_seq_id = 1
422
- self .__connected_stream = True
423
476
424
- def fetchone (self ):
477
+ def fetchone (self , force_reconnect = False ):
478
+ self .__prefetch (force_reconnect = force_reconnect )
479
+ return self .__fetchone ()
480
+
481
+ def __prefetch (self , force_reconnect = False ):
482
+ self .__connect_to_ctl (force_reconnect = force_reconnect )
483
+ self .__connect_to_stream (force_reconnect = force_reconnect )
484
+
485
+ def __fetchone (self ):
486
+ # let `__fetchone` be as light weight as possible.
425
487
while True :
426
488
if self .end_log_pos and self .is_past_end_log_pos :
427
489
return None
428
490
429
- if not self .__connected_stream :
430
- self .__connect_to_stream ()
431
-
432
- if not self .__connected_ctl :
433
- self .__connect_to_ctl ()
434
-
435
491
try :
436
- if pymysql .__version__ < LooseVersion ("0.6" ):
437
- pkt = self ._stream_connection .read_packet ()
438
- else :
439
- pkt = self ._stream_connection ._read_packet ()
492
+ pkt = self ._stream_connection ._read_packet ()
440
493
except pymysql .OperationalError as error :
441
494
code , message = error .args
442
495
if code in MYSQL_EXPECTED_ERROR_CODES :
443
- self ._stream_connection .close ()
444
- self .__connected_stream = False
496
+ self .__connect_to_stream (force_reconnect = True )
445
497
continue
446
498
raise
447
499
@@ -555,9 +607,8 @@ def _allowed_event_list(self, only_events, ignored_events,
555
607
556
608
def __get_table_information (self , schema , table ):
557
609
for i in range (1 , 3 ):
610
+ self .__connect_to_ctl ()
558
611
try :
559
- if not self .__connected_ctl :
560
- self .__connect_to_ctl ()
561
612
562
613
cur = self ._ctl_connection .cursor ()
563
614
cur .execute ("""
@@ -575,10 +626,10 @@ def __get_table_information(self, schema, table):
575
626
except pymysql .OperationalError as error :
576
627
code , message = error .args
577
628
if code in MYSQL_EXPECTED_ERROR_CODES :
578
- self .__connected_ctl = False
579
629
continue
580
630
else :
581
631
raise error
582
632
583
633
def __iter__ (self ):
584
- return iter (self .fetchone , None )
634
+ self .__prefetch (force_reconnect = False )
635
+ return iter (self .__fetchone , None )
0 commit comments