mirror of
				https://github.com/psycopg/psycopg2.git
				synced 2025-11-04 01:37:31 +03:00 
			
		
		
		
	Replace stop_replication with requirement for an exception.
This commit is contained in:
		
							parent
							
								
									0435320f34
								
							
						
					
					
						commit
						4ab7cf0157
					
				| 
						 | 
					@ -348,9 +348,11 @@ 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()``.  In order to make this
 | 
					        the server and passing them to ``consume()``, then waiting for more
 | 
				
			||||||
        method break out of the loop and return, ``consume()`` can call
 | 
					        messages from the server.  In order to make this method break out of
 | 
				
			||||||
        `stop_replication()` on the cursor or it can throw an exception.
 | 
					        the loop and return, ``consume()`` can throw a `StopReplication`
 | 
				
			||||||
 | 
					        exception (any unhandled exception will make it break out of the loop
 | 
				
			||||||
 | 
					        as well).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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
 | 
				
			||||||
| 
						 | 
					@ -398,13 +400,6 @@ The individual messages in the replication stream are presented by
 | 
				
			||||||
            load on network and the server.  A possible strategy is to confirm
 | 
					            load on network and the server.  A possible strategy is to confirm
 | 
				
			||||||
            after every COMMIT message.
 | 
					            after every COMMIT message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. method:: stop_replication()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        This method can be called on synchronous connection from the
 | 
					 | 
				
			||||||
        ``consume()`` callable in order to break out of the endless loop in
 | 
					 | 
				
			||||||
        `consume_replication_stream()`.  If called on asynchronous connection
 | 
					 | 
				
			||||||
        or when replication is not in progress, this method 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param write_lsn: a LSN position up to which the client has written the data locally
 | 
					        :param write_lsn: a LSN position up to which the client has written the data locally
 | 
				
			||||||
| 
						 | 
					@ -506,10 +501,11 @@ The individual messages in the replication stream are presented by
 | 
				
			||||||
                if not sel[0]:
 | 
					                if not sel[0]:
 | 
				
			||||||
                    cur.send_replication_feedback()
 | 
					                    cur.send_replication_feedback()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
.. index::
 | 
					.. index::
 | 
				
			||||||
    pair: Cursor; Replication
 | 
					    pair: Cursor; Replication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. autoclass:: StopReplication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. index::
 | 
					.. index::
 | 
				
			||||||
    single: Data types; Additional
 | 
					    single: Data types; Additional
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -500,6 +500,18 @@ class PhysicalReplicationConnection(ReplicationConnectionBase):
 | 
				
			||||||
        super(PhysicalReplicationConnection, self).__init__(*args, **kwargs)
 | 
					        super(PhysicalReplicationConnection, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StopReplication(Exception):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Exception used to break out of the endless loop in
 | 
				
			||||||
 | 
					    `~ReplicationCursor.consume_replication_stream()`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Subclass of `~exceptions.Exception`.  Intentionally *not* inherited from
 | 
				
			||||||
 | 
					    `~psycopg2.Error` as occurrence of this exception does not indicate an
 | 
				
			||||||
 | 
					    error.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReplicationCursor(_cursor):
 | 
					class ReplicationCursor(_cursor):
 | 
				
			||||||
    """A cursor used for communication on the replication protocol."""
 | 
					    """A cursor used for communication on the replication protocol."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,6 @@ struct cursorObject {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* replication cursor attrs */
 | 
					    /* replication cursor attrs */
 | 
				
			||||||
    int         repl_started:1;          /* if replication is started */
 | 
					    int         repl_started:1;          /* if replication is started */
 | 
				
			||||||
    int         repl_stop:1;             /* if client requested to stop replication */
 | 
					 | 
				
			||||||
    int         repl_consuming:1;        /* if running the consume loop */
 | 
					    int         repl_consuming:1;        /* if running the consume loop */
 | 
				
			||||||
    struct timeval repl_keepalive_interval;   /* interval for keepalive messages in replication mode */
 | 
					    struct timeval repl_keepalive_interval;   /* interval for keepalive messages in replication mode */
 | 
				
			||||||
    XLogRecPtr  repl_write_lsn;        /* LSN stats for replication feedback messages */
 | 
					    XLogRecPtr  repl_write_lsn;        /* LSN stats for replication feedback messages */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1605,7 +1605,6 @@ psyco_curs_start_replication_expert(cursorObject *self, PyObject *args, PyObject
 | 
				
			||||||
    Dprintf("psyco_curs_start_replication_expert: %s", command);
 | 
					    Dprintf("psyco_curs_start_replication_expert: %s", command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self->copysize = 0;
 | 
					    self->copysize = 0;
 | 
				
			||||||
    self->repl_stop = 0;
 | 
					 | 
				
			||||||
    self->repl_consuming = 0;
 | 
					    self->repl_consuming = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self->repl_write_lsn = InvalidXLogRecPtr;
 | 
					    self->repl_write_lsn = InvalidXLogRecPtr;
 | 
				
			||||||
| 
						 | 
					@ -1626,21 +1625,6 @@ psyco_curs_start_replication_expert(cursorObject *self, PyObject *args, PyObject
 | 
				
			||||||
    return res;
 | 
					    return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define psyco_curs_stop_replication_doc \
 | 
					 | 
				
			||||||
"stop_replication() -- Set flag to break out of the endless loop in consume_replication_stream()."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static PyObject *
 | 
					 | 
				
			||||||
psyco_curs_stop_replication(cursorObject *self)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    EXC_IF_CURS_CLOSED(self);
 | 
					 | 
				
			||||||
    EXC_IF_CURS_ASYNC(self, stop_replication);
 | 
					 | 
				
			||||||
    EXC_IF_NOT_REPLICATING(self, stop_replication);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    self->repl_stop = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Py_RETURN_NONE;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define psyco_curs_consume_replication_stream_doc \
 | 
					#define psyco_curs_consume_replication_stream_doc \
 | 
				
			||||||
"consume_replication_stream(consumer, keepalive_interval=10) -- Consume replication stream."
 | 
					"consume_replication_stream(consumer, keepalive_interval=10) -- Consume replication stream."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1684,7 +1668,6 @@ psyco_curs_consume_replication_stream(cursorObject *self, PyObject *args, PyObje
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self->repl_consuming = 0;
 | 
					    self->repl_consuming = 0;
 | 
				
			||||||
    self->repl_stop = 0; /* who knows, what if we will be called again? */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return res;
 | 
					    return res;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1992,8 +1975,6 @@ static struct PyMethodDef cursorObject_methods[] = {
 | 
				
			||||||
     METH_VARARGS|METH_KEYWORDS, psyco_curs_copy_expert_doc},
 | 
					     METH_VARARGS|METH_KEYWORDS, psyco_curs_copy_expert_doc},
 | 
				
			||||||
    {"start_replication_expert", (PyCFunction)psyco_curs_start_replication_expert,
 | 
					    {"start_replication_expert", (PyCFunction)psyco_curs_start_replication_expert,
 | 
				
			||||||
     METH_VARARGS|METH_KEYWORDS, psyco_curs_start_replication_expert_doc},
 | 
					     METH_VARARGS|METH_KEYWORDS, psyco_curs_start_replication_expert_doc},
 | 
				
			||||||
    {"stop_replication", (PyCFunction)psyco_curs_stop_replication,
 | 
					 | 
				
			||||||
     METH_NOARGS, psyco_curs_stop_replication_doc},
 | 
					 | 
				
			||||||
    {"consume_replication_stream", (PyCFunction)psyco_curs_consume_replication_stream,
 | 
					    {"consume_replication_stream", (PyCFunction)psyco_curs_consume_replication_stream,
 | 
				
			||||||
     METH_VARARGS|METH_KEYWORDS, psyco_curs_consume_replication_stream_doc},
 | 
					     METH_VARARGS|METH_KEYWORDS, psyco_curs_consume_replication_stream_doc},
 | 
				
			||||||
    {"read_replication_message", (PyCFunction)psyco_curs_read_replication_message,
 | 
					    {"read_replication_message", (PyCFunction)psyco_curs_read_replication_message,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1743,7 +1743,7 @@ pq_copy_both(cursorObject *curs, PyObject *consume, int decode, double keepalive
 | 
				
			||||||
    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 (!curs->repl_stop) {
 | 
					    while (1) {
 | 
				
			||||||
        msg = pq_read_replication_message(curs, decode);
 | 
					        msg = pq_read_replication_message(curs, decode);
 | 
				
			||||||
        if (!msg) {
 | 
					        if (!msg) {
 | 
				
			||||||
            goto exit;
 | 
					            goto exit;
 | 
				
			||||||
| 
						 | 
					@ -1803,11 +1803,6 @@ pq_copy_both(cursorObject *curs, PyObject *consume, int decode, double keepalive
 | 
				
			||||||
                goto exit;
 | 
					                goto exit;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Py_DECREF(tmp);
 | 
					            Py_DECREF(tmp);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (curs->repl_stop) {
 | 
					 | 
				
			||||||
                Dprintf("pq_copy_both: repl_stop flag set by consume_func");
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@
 | 
				
			||||||
import psycopg2
 | 
					import psycopg2
 | 
				
			||||||
import psycopg2.extensions
 | 
					import psycopg2.extensions
 | 
				
			||||||
from psycopg2.extras import PhysicalReplicationConnection, LogicalReplicationConnection
 | 
					from psycopg2.extras import PhysicalReplicationConnection, LogicalReplicationConnection
 | 
				
			||||||
 | 
					from psycopg2.extras import StopReplication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from testutils import unittest
 | 
					from testutils import unittest
 | 
				
			||||||
from testutils import skip_before_postgres
 | 
					from testutils import skip_before_postgres
 | 
				
			||||||
| 
						 | 
					@ -77,20 +78,6 @@ class ReplicationTest(ReplicationTestCase):
 | 
				
			||||||
        cur.execute("IDENTIFY_SYSTEM")
 | 
					        cur.execute("IDENTIFY_SYSTEM")
 | 
				
			||||||
        cur.fetchall()
 | 
					        cur.fetchall()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @skip_before_postgres(9, 0)
 | 
					 | 
				
			||||||
    def test_stop_replication_raises(self):
 | 
					 | 
				
			||||||
        conn = self.repl_connect(connection_factory=PhysicalReplicationConnection)
 | 
					 | 
				
			||||||
        if conn is None: return
 | 
					 | 
				
			||||||
        cur = conn.cursor()
 | 
					 | 
				
			||||||
        self.assertRaises(psycopg2.ProgrammingError, cur.stop_replication)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cur.start_replication()
 | 
					 | 
				
			||||||
        cur.stop_replication() # doesn't raise now
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def consume(msg):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        cur.consume_replication_stream(consume) # should return at once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @skip_before_postgres(9, 4) # slots require 9.4
 | 
					    @skip_before_postgres(9, 4) # slots require 9.4
 | 
				
			||||||
    def test_create_replication_slot(self):
 | 
					    def test_create_replication_slot(self):
 | 
				
			||||||
        conn = self.repl_connect(connection_factory=PhysicalReplicationConnection)
 | 
					        conn = self.repl_connect(connection_factory=PhysicalReplicationConnection)
 | 
				
			||||||
| 
						 | 
					@ -115,6 +102,36 @@ class ReplicationTest(ReplicationTestCase):
 | 
				
			||||||
        self.create_replication_slot(cur, slot)
 | 
					        self.create_replication_slot(cur, slot)
 | 
				
			||||||
        cur.start_replication(slot)
 | 
					        cur.start_replication(slot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @skip_before_postgres(9, 4) # slots require 9.4
 | 
				
			||||||
 | 
					    def test_stop_replication(self):
 | 
				
			||||||
 | 
					        conn = self.repl_connect(connection_factory=LogicalReplicationConnection)
 | 
				
			||||||
 | 
					        if conn is None: return
 | 
				
			||||||
 | 
					        cur = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        slot = "test_slot1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.create_replication_slot(cur, slot, output_plugin='test_decoding')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.make_replication_event()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cur.start_replication(slot)
 | 
				
			||||||
 | 
					        def consume(msg):
 | 
				
			||||||
 | 
					            raise StopReplication()
 | 
				
			||||||
 | 
					        self.assertRaises(StopReplication, cur.consume_replication_stream, consume)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # generate an event for our replication stream
 | 
				
			||||||
 | 
					    def make_replication_event(self):
 | 
				
			||||||
 | 
					        conn = self.connect()
 | 
				
			||||||
 | 
					        if conn is None: return
 | 
				
			||||||
 | 
					        cur = conn.cursor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            cur.execute("DROP TABLE dummy1")
 | 
				
			||||||
 | 
					        except psycopg2.ProgrammingError:
 | 
				
			||||||
 | 
					            conn.rollback()
 | 
				
			||||||
 | 
					        cur.execute("CREATE TABLE dummy1()")
 | 
				
			||||||
 | 
					        conn.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AsyncReplicationTest(ReplicationTestCase):
 | 
					class AsyncReplicationTest(ReplicationTestCase):
 | 
				
			||||||
    @skip_before_postgres(9, 4)
 | 
					    @skip_before_postgres(9, 4)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user