mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-26 02:43:43 +03:00
Update stop_repl, require replication consumer to be a callable.
This commit is contained in:
parent
fea2260fc5
commit
a0b42a12ff
|
@ -333,9 +333,9 @@ The individual messages in the replication stream are presented by
|
||||||
Start replication on the connection using provided ``START_REPLICATION``
|
Start replication on the connection using provided ``START_REPLICATION``
|
||||||
command.
|
command.
|
||||||
|
|
||||||
.. method:: consume_replication_stream(consumer, decode=False, keepalive_interval=10)
|
.. method:: consume_replication_stream(consume, decode=False, keepalive_interval=10)
|
||||||
|
|
||||||
:param consumer: an object providing ``consume()`` method
|
:param consume: a callable object with signature ``consume(msg)``
|
||||||
:param decode: a flag indicating that unicode conversion should be
|
:param decode: a flag indicating that unicode conversion should be
|
||||||
performed on the messages received from the server
|
performed on the messages received from the server
|
||||||
:param keepalive_interval: interval (in seconds) to send keepalive
|
:param keepalive_interval: interval (in seconds) to send keepalive
|
||||||
|
@ -348,10 +348,9 @@ The individual messages in the replication stream are presented by
|
||||||
`start_replication()` first.
|
`start_replication()` first.
|
||||||
|
|
||||||
When called, this method enters an endless loop, reading messages from
|
When called, this method enters an endless loop, reading messages from
|
||||||
the server and passing them to ``consume()`` method of the *consumer*
|
the server and passing them to ``consume()``. In order to make this
|
||||||
object. In order to make this method break out of the loop and
|
method break out of the loop and return, ``consume()`` can call
|
||||||
return, the ``consume()`` method can call `stop_replication()` on the
|
`stop_replication()` on the cursor or it can throw an exception.
|
||||||
cursor or it can throw an exception.
|
|
||||||
|
|
||||||
If *decode* is set to `!True`, the messages read from the server are
|
If *decode* is set to `!True`, the messages read from the server are
|
||||||
converted according to the connection `~connection.encoding`. This
|
converted according to the connection `~connection.encoding`. This
|
||||||
|
@ -362,12 +361,12 @@ The individual messages in the replication stream are presented by
|
||||||
*keepalive_interval* (in seconds). The value of this parameter must
|
*keepalive_interval* (in seconds). The value of this parameter must
|
||||||
be equal to at least 1 second, but it can have a fractional part.
|
be equal to at least 1 second, but it can have a fractional part.
|
||||||
|
|
||||||
The following example is a sketch implementation of *consumer* object
|
The following example is a sketch implementation of ``consume()``
|
||||||
for logical replication::
|
callable for logical replication::
|
||||||
|
|
||||||
class LogicalStreamConsumer(object):
|
class LogicalStreamConsumer(object):
|
||||||
|
|
||||||
def consume(self, msg):
|
def __call__(self, msg):
|
||||||
self.store_message_data(msg.payload)
|
self.store_message_data(msg.payload)
|
||||||
|
|
||||||
if self.should_report_to_the_server_now(msg):
|
if self.should_report_to_the_server_now(msg):
|
||||||
|
@ -376,7 +375,7 @@ The individual messages in the replication stream are presented by
|
||||||
consumer = LogicalStreamConsumer()
|
consumer = LogicalStreamConsumer()
|
||||||
cur.consume_replication_stream(consumer, decode=True)
|
cur.consume_replication_stream(consumer, decode=True)
|
||||||
|
|
||||||
The *msg* objects passed to the ``consume()`` method are instances of
|
The *msg* objects passed to ``consume()`` are instances of
|
||||||
`ReplicationMessage` class.
|
`ReplicationMessage` class.
|
||||||
|
|
||||||
After storing certain amount of messages' data reliably, the client
|
After storing certain amount of messages' data reliably, the client
|
||||||
|
@ -401,11 +400,10 @@ The individual messages in the replication stream are presented by
|
||||||
|
|
||||||
.. method:: stop_replication()
|
.. method:: stop_replication()
|
||||||
|
|
||||||
This method can be called on synchronous connections from the
|
This method can be called on synchronous connection from the
|
||||||
``consume()`` method of a ``consumer`` object in order to break out of
|
``consume()`` callable in order to break out of the endless loop in
|
||||||
the endless loop in `consume_replication_stream()`. If called on
|
`consume_replication_stream()`. If called on asynchronous connection
|
||||||
asynchronous connection or outside of the consume loop, this method
|
or when replication is not in progress, this method raises an error.
|
||||||
raises an error.
|
|
||||||
|
|
||||||
.. method:: send_replication_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False)
|
.. method:: send_replication_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False)
|
||||||
|
|
||||||
|
@ -490,11 +488,14 @@ The individual messages in the replication stream are presented by
|
||||||
|
|
||||||
An actual example of asynchronous operation might look like this::
|
An actual example of asynchronous operation might look like this::
|
||||||
|
|
||||||
|
def consume(msg):
|
||||||
|
...
|
||||||
|
|
||||||
keepalive_interval = 10.0
|
keepalive_interval = 10.0
|
||||||
while True:
|
while True:
|
||||||
msg = cur.read_replication_message()
|
msg = cur.read_replication_message()
|
||||||
if msg:
|
if msg:
|
||||||
consumer.consume(msg)
|
consume(msg)
|
||||||
else:
|
else:
|
||||||
timeout = keepalive_interval - (datetime.now() - cur.replication_io_timestamp).total_seconds()
|
timeout = keepalive_interval - (datetime.now() - cur.replication_io_timestamp).total_seconds()
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
|
|
|
@ -1622,13 +1622,15 @@ psyco_curs_start_replication_expert(cursorObject *self, PyObject *args, PyObject
|
||||||
1 /* no_result */, 1 /* no_begin */) >= 0) {
|
1 /* no_result */, 1 /* no_begin */) >= 0) {
|
||||||
res = Py_None;
|
res = Py_None;
|
||||||
Py_INCREF(res);
|
Py_INCREF(res);
|
||||||
|
|
||||||
|
self->repl_started = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define psyco_curs_stop_replication_doc \
|
#define psyco_curs_stop_replication_doc \
|
||||||
"stop_replication() -- Set flag to break out of endless loop in start_replication() on sync connection."
|
"stop_replication() -- Set flag to break out of the endless loop in consume_replication_stream()."
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
psyco_curs_stop_replication(cursorObject *self)
|
psyco_curs_stop_replication(cursorObject *self)
|
||||||
|
@ -1652,13 +1654,13 @@ psyco_curs_stop_replication(cursorObject *self)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
psyco_curs_consume_replication_stream(cursorObject *self, PyObject *args, PyObject *kwargs)
|
psyco_curs_consume_replication_stream(cursorObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
PyObject *consumer = NULL, *res = NULL;
|
PyObject *consume = NULL, *res = NULL;
|
||||||
int decode = 0;
|
int decode = 0;
|
||||||
double keepalive_interval = 10;
|
double keepalive_interval = 10;
|
||||||
static char *kwlist[] = {"consumer", "decode", "keepalive_interval", NULL};
|
static char *kwlist[] = {"consume", "decode", "keepalive_interval", NULL};
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|id", kwlist,
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|id", kwlist,
|
||||||
&consumer, &decode, &keepalive_interval)) {
|
&consume, &decode, &keepalive_interval)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1674,9 +1676,7 @@ psyco_curs_consume_replication_stream(cursorObject *self, PyObject *args, PyObje
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->repl_started = 1;
|
if (pq_copy_both(self, consume, decode, keepalive_interval) >= 0) {
|
||||||
|
|
||||||
if (pq_copy_both(self, consumer, decode, keepalive_interval) >= 0) {
|
|
||||||
res = Py_None;
|
res = Py_None;
|
||||||
Py_INCREF(res);
|
Py_INCREF(res);
|
||||||
}
|
}
|
||||||
|
@ -1709,7 +1709,7 @@ static PyObject *
|
||||||
curs_flush_replication_feedback(cursorObject *self, int reply)
|
curs_flush_replication_feedback(cursorObject *self, int reply)
|
||||||
{
|
{
|
||||||
if (!(self->repl_feedback_pending || reply))
|
if (!(self->repl_feedback_pending || reply))
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_TRUE;
|
||||||
|
|
||||||
if (pq_send_replication_feedback(self, reply)) {
|
if (pq_send_replication_feedback(self, reply)) {
|
||||||
self->repl_feedback_pending = 0;
|
self->repl_feedback_pending = 0;
|
||||||
|
|
|
@ -1723,7 +1723,7 @@ pq_send_replication_feedback(cursorObject* curs, int reply_requested)
|
||||||
manages to send keepalive messages to the server as needed.
|
manages to send keepalive messages to the server as needed.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
pq_copy_both(cursorObject *curs, PyObject *consumer, int decode, double keepalive_interval)
|
pq_copy_both(cursorObject *curs, PyObject *consume, int decode, double keepalive_interval)
|
||||||
{
|
{
|
||||||
PyObject *msg, *tmp = NULL;
|
PyObject *msg, *tmp = NULL;
|
||||||
PyObject *consume_func = NULL;
|
PyObject *consume_func = NULL;
|
||||||
|
@ -1732,8 +1732,8 @@ pq_copy_both(cursorObject *curs, PyObject *consumer, int decode, double keepaliv
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
struct timeval keep_intr, curr_time, ping_time, timeout;
|
struct timeval keep_intr, curr_time, ping_time, timeout;
|
||||||
|
|
||||||
if (!(consume_func = PyObject_GetAttrString(consumer, "consume"))) {
|
if (!(consume_func = PyObject_GetAttrString(consume, "__call__"))) {
|
||||||
Dprintf("pq_copy_both: can't get o.consume");
|
Dprintf("pq_copy_both: expected callable consume object");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1743,7 +1743,7 @@ pq_copy_both(cursorObject *curs, PyObject *consumer, int decode, double keepaliv
|
||||||
keep_intr.tv_sec = (int)keepalive_interval;
|
keep_intr.tv_sec = (int)keepalive_interval;
|
||||||
keep_intr.tv_usec = (keepalive_interval - keep_intr.tv_sec)*1.0e6;
|
keep_intr.tv_usec = (keepalive_interval - keep_intr.tv_sec)*1.0e6;
|
||||||
|
|
||||||
while (1) {
|
while (!curs->repl_stop) {
|
||||||
msg = pq_read_replication_message(curs, decode);
|
msg = pq_read_replication_message(curs, decode);
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
|
@ -1206,7 +1206,11 @@ class ReplicationTest(ConnectingTestCase):
|
||||||
self.assertRaises(psycopg2.ProgrammingError, cur.stop_replication)
|
self.assertRaises(psycopg2.ProgrammingError, cur.stop_replication)
|
||||||
|
|
||||||
cur.start_replication()
|
cur.start_replication()
|
||||||
self.assertRaises(psycopg2.ProgrammingError, cur.stop_replication)
|
cur.stop_replication() # doesn't raise now
|
||||||
|
|
||||||
|
def consume(msg):
|
||||||
|
pass
|
||||||
|
cur.consume_replication_stream(consume) # should return at once
|
||||||
|
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
|
|
Loading…
Reference in New Issue
Block a user