From: Michael Wallner Date: Tue, 17 May 2016 14:25:18 +0000 (+0200) Subject: restore listeners and statements on connection reset X-Git-Tag: v1.1.0~4 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-pq;a=commitdiff_plain;h=df586b558795570822225a3a458bd91a1f7ab433 restore listeners and statements on connection reset closes issue #15 --- diff --git a/package.xml b/package.xml index 70a4eca..917e7eb 100644 --- a/package.xml +++ b/package.xml @@ -47,6 +47,7 @@ BSD-2-Clause @@ -126,6 +127,8 @@ + + diff --git a/src/php_pqconn.c b/src/php_pqconn.c index 1caf4d8..ed7cbe1 100644 --- a/src/php_pqconn.c +++ b/src/php_pqconn.c @@ -78,6 +78,7 @@ static void php_pqconn_object_free(void *o TSRMLS_DC) php_resource_factory_handle_dtor(&obj->intern->factory, obj->intern->conn TSRMLS_CC); php_resource_factory_dtor(&obj->intern->factory); zend_hash_destroy(&obj->intern->listeners); + zend_hash_destroy(&obj->intern->statements); zend_hash_destroy(&obj->intern->converters); zend_hash_destroy(&obj->intern->eventhandlers); efree(obj->intern); @@ -679,6 +680,7 @@ static PHP_METHOD(pqconn, __construct) { obj->intern->default_auto_convert = PHP_PQRES_CONV_ALL; zend_hash_init(&obj->intern->listeners, 0, NULL, (dtor_func_t) zend_hash_destroy, 0); + zend_hash_init(&obj->intern->statements, 0, NULL, NULL, 0); zend_hash_init(&obj->intern->converters, 0, NULL, ZVAL_PTR_DTOR, 0); zend_hash_init(&obj->intern->eventhandlers, 0, NULL, (dtor_func_t) zend_hash_destroy, 0); diff --git a/src/php_pqconn.h b/src/php_pqconn.h index acec39b..195da5b 100644 --- a/src/php_pqconn.h +++ b/src/php_pqconn.h @@ -27,6 +27,7 @@ typedef struct php_pqconn { int (*poller)(PGconn *); php_resource_factory_t factory; HashTable listeners; + HashTable statements; HashTable converters; HashTable eventhandlers; php_pq_callback_t onevent; diff --git a/src/php_pqconn_event.c b/src/php_pqconn_event.c index 541892f..64d94b7 100644 --- a/src/php_pqconn_event.c +++ b/src/php_pqconn_event.c @@ -16,12 +16,16 @@ #include +#define SMART_STR_PREALLOC 256 +#include + #include #include "php_pq.h" #include "php_pq_misc.h" #include "php_pq_object.h" #include "php_pqconn_event.h" +#include "php_pqstm.h" #include "php_pqres.h" static int apply_event(void *p, void *a TSRMLS_DC) @@ -39,6 +43,50 @@ static int apply_event(void *p, void *a TSRMLS_DC) return ZEND_HASH_APPLY_KEEP; } + +static inline PGresult *relisten(PGconn *conn, const char *channel_str, size_t channel_len TSRMLS_DC) +{ + char *quoted_channel = PQescapeIdentifier(conn, channel_str, channel_len); + PGresult *res = NULL; + + if (quoted_channel) { + smart_str cmd = {0}; + + smart_str_appends(&cmd, "LISTEN "); + smart_str_appends(&cmd, quoted_channel); + smart_str_0(&cmd); + + res = PQexec(conn, cmd.c); + + smart_str_free(&cmd); + PQfreemem(quoted_channel); + } + + return res; +} + +static int apply_relisten(void *p TSRMLS_DC, int argc, va_list argv, zend_hash_key *key) +{ + php_pqconn_object_t *obj = va_arg(argv, php_pqconn_object_t *); + PGresult *res = relisten(obj->intern->conn, key->arKey, key->nKeyLength - 1 TSRMLS_CC); + + if (res) { + php_pqres_clear(res); + } + + return ZEND_HASH_APPLY_KEEP; +} + +static int apply_reprepare(void *p TSRMLS_DC, int argc, va_list argv, zend_hash_key *key) +{ + php_pqconn_object_t *obj = va_arg(argv, php_pqconn_object_t *); + php_pqstm_t *stm = *(php_pqstm_object_t **) p; + + php_pqconn_prepare(NULL, obj, stm->name, stm->query, stm->params TSRMLS_CC); + + return ZEND_HASH_APPLY_KEEP; +} + static void php_pqconn_event_connreset(PGEventConnReset *event) { php_pqconn_event_data_t *data = PQinstanceData(event->conn, php_pqconn_event); @@ -47,6 +95,13 @@ static void php_pqconn_event_connreset(PGEventConnReset *event) HashTable *evhs; TSRMLS_DF(data); + /* restore listeners */ + zend_hash_apply_with_arguments(&data->obj->intern->listeners TSRMLS_CC, apply_relisten, 1, data->obj); + + /* restore statements */ + zend_hash_apply_with_arguments(&data->obj->intern->statements TSRMLS_CC, apply_reprepare, 1, data->obj); + + /* eventhandler */ if (SUCCESS == zend_hash_find(&data->obj->intern->eventhandlers, ZEND_STRS("reset"), (void *) &evhs)) { zval *args, *connection = NULL; diff --git a/src/php_pqstm.c b/src/php_pqstm.c index b82adcf..0f74b2b 100644 --- a/src/php_pqstm.c +++ b/src/php_pqstm.c @@ -63,6 +63,7 @@ static void php_pqstm_deallocate(php_pqstm_object_t *obj, zend_bool async, zend_ } obj->intern->allocated = 0; + zend_hash_del(&obj->intern->conn->intern->statements, obj->intern->name, strlen(obj->intern->name)+1); } } @@ -164,6 +165,8 @@ php_pqstm_t *php_pqstm_init(php_pqconn_object_t *conn, const char *name, const c ZEND_INIT_SYMTABLE(&stm->bound); + zend_hash_add(&conn->intern->statements, name, strlen(name)+1, &stm, sizeof(stm), NULL); + return stm; } @@ -420,7 +423,7 @@ static PHP_METHOD(pqstm, deallocateAsync) php_pqstm_deallocate_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } -static zend_always_inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAMETERS, zend_bool async) +static inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAMETERS, zend_bool async) { zend_error_handling zeh; ZEND_RESULT_CODE rv; @@ -443,6 +446,9 @@ static zend_always_inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAM if (SUCCESS == rv) { obj->intern->allocated = 1; + + zend_hash_add(&obj->intern->conn->intern->statements, + obj->intern->name, strlen(obj->intern->name)+1, &obj, sizeof(obj), NULL); } } } diff --git a/tests/gh-issue015_listeners.phpt b/tests/gh-issue015_listeners.phpt new file mode 100644 index 0000000..5506b21 --- /dev/null +++ b/tests/gh-issue015_listeners.phpt @@ -0,0 +1,70 @@ +--TEST-- +restore listeners on reset +--SKIPIF-- + +--INI-- +date.timezone=UTC +--FILE-- +listen("notify", function($channel, $message) { + printf("%s: %s\n", $channel, $message); +}); +$c->on(pq\Connection::EVENT_RESET, function($conn) { + printf("Connection was reset\n"); +}); +$c->notify("notify", "Gotcha!"); +$c->resetAsync(); + +// wait until the stream becomes writable +$w = array($c->socket); +$r = $e = null; + +if (stream_select($r, $w, $e, null)) { + + // loop until the connection is established + while (true) { + + switch ($c->poll()) { + + case pq\Connection::POLLING_READING: + // we should wait for the stream to be read-ready + $r = array($c->socket); + stream_select($r, $w, $e, NULL); + break; + + case pq\Connection::POLLING_WRITING: + // we should wait for the stream to be write-ready + $w = array($c->socket); + $r = $e = null; + stream_select($r, $w, $e, null); + break; + + case pq\Connection::POLLING_FAILED: + printf("Connection failed: %s\n", $c->errorMessage); + break 2; + + case pq\Connection::POLLING_OK: + printf("Connection completed\n"); + break 2; + } + } +} +$c->notify("notify", "Do you miss me?"); +$c->exec(""); +?> +===DONE=== +--EXPECT-- +Test +notify: Gotcha! +Connection was reset +Connection completed +notify: Do you miss me? +===DONE=== \ No newline at end of file diff --git a/tests/gh-issue015_statements.phpt b/tests/gh-issue015_statements.phpt new file mode 100644 index 0000000..b54664a --- /dev/null +++ b/tests/gh-issue015_statements.phpt @@ -0,0 +1,42 @@ +--TEST-- +restore statements on reset +--SKIPIF-- + +--INI-- +date.timezone=UTC +--FILE-- +prepare("test", "SELECT 1"); +$c->on(pq\Connection::EVENT_RESET, function($conn) { + printf("Connection was reset\n"); +}); + +var_dump($s->exec()->fetchRow()); + +$c->reset(); + +// Fatal error: Uncaught exception 'pq\Exception\DomainException' with message 'ERROR: prepared statement "test" does not exist' +var_dump($s->exec()->fetchRow()); + +?> +===DONE=== +--EXPECT-- +Test +array(1) { + [0]=> + int(1) +} +Connection was reset +array(1) { + [0]=> + int(1) +} +===DONE=== \ No newline at end of file