Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Feature error handler #23

Closed
wants to merge 29 commits into from

2 participants

@theintz

I have moved all the work I did for setting an error handler onto a separate branch. This pull request includes all the commits. It replaces #19.

It extends the original pull request by allowing the error handler to be called from all functions, not just the non-multi functions. It is further called for connection errors as well.

theintz added some commits
@theintz theintz added test case 27 for testing error handlers b17c2aa
@theintz theintz added PHPIREDIS_ERROR_CONNECTION and PHPIREDIS_ERROR_PROTOCOL constan…
…ts, added phpiredis_set_error_handler() function with copied code
e2c0305
@theintz theintz added error callback to _phpiredis_connection struct 482cb39
@theintz theintz use a proper callback in the test 5ee3e9c
@theintz theintz expanded test, checks for function present and does a faulty operation e7751e1
@theintz theintz proper function declaration for free_error_handler(), use the right m…
…acros for accessing arguments
3af0f17
@theintz theintz removed superfluous returns d6c4be9
@theintz theintz removed TODO from phpiredis_set_error_handler() 5ffb663
@theintz theintz call error handler if it is set 0274f21
@theintz theintz expanded test to check the *_bs functions as well ae60f14
@theintz theintz extend test with checks for some error conditions 057220a
@theintz theintz tabs to spaces fb1b28f
@theintz theintz refactored error handler calling code into separate function 184cf6b
@theintz theintz free error callback when destroying the connection 6ae98d6
@theintz theintz need to include the thread safety parameters be0f219
@theintz theintz removed merge error marks....... again bfb4de1
@theintz theintz modified signature of handle_error_callback() to accept a string inst…
…ead of an redisReply
75d60df
@theintz theintz call error handler for connection errors also 3965dbb
@theintz theintz refactored callback calling again to reduce some duplicate code 18c2183
@theintz theintz set an empty error handler in test 008 c175422
@theintz theintz added test case for corrct error handling with dead connections using…
… QUIT command
5bc10f3
@theintz theintz set an empty error handler in test 009 ee28f42
@theintz theintz added test case 029 for testing error handler invocation when using m…
…ulti commands
910a3d5
@theintz theintz call error handler in multi functions as well 892ada1
@theintz theintz added test for proper invocation of error handler when using multi me…
…thods on connection errors
9d3d703
@theintz theintz fix segfault which occurred when an error handler was set and unset a…
…gain (which was done automatically when setting a new error handler) and then called from outside.

the reason why this happened is that the actual php object was unset unintentionally. the commit also contains a test to check for this behavior. i am unsure if the implementation is 100% correct, there may be a memory leak.
c00267b
@seppo0010 seppo0010 commented on the diff
phpiredis.c
((32 lines not shown))
+ ZVAL_LONG(arg[0], type);
+ MAKE_STD_ZVAL(arg[1]);
+
+ // only set second argument when msg is given
+ if (msg != NULL && len > 0) {
+ ZVAL_STRINGL(arg[1], msg, len, 1);
+ }
+
+ MAKE_STD_ZVAL(return_value);
+
+ if (call_user_function(EG(function_table), NULL, ((callback*) connection->error_callback)->function, return_value, 2, arg TSRMLS_CC) == FAILURE) {
+ // return FAILURE to signal something went wrong with the error handler
+ retval = FAILURE;
+ }
+
+ // TODO: we could also return whatever the error handler returned to allow for more flexibility
@seppo0010 Collaborator

Isn't this relatively easy to address?

@theintz
theintz added a note

@seppo0010 yes, it would be a simple change, but I didn't see a use case for that. we could implement it, if you think it's useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@nrk nrk referenced this pull request
Open

Tag a release #29

@theintz theintz closed this
@theintz theintz deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 18, 2014
  1. @theintz
  2. @theintz

    added PHPIREDIS_ERROR_CONNECTION and PHPIREDIS_ERROR_PROTOCOL constan…

    theintz authored
    …ts, added phpiredis_set_error_handler() function with copied code
  3. @theintz
  4. @theintz
  5. @theintz
  6. @theintz

    proper function declaration for free_error_handler(), use the right m…

    theintz authored
    …acros for accessing arguments
  7. @theintz

    removed superfluous returns

    theintz authored
  8. @theintz
  9. @theintz
  10. @theintz
  11. @theintz
  12. @theintz

    tabs to spaces

    theintz authored
  13. @theintz
  14. @theintz
  15. @theintz
  16. @theintz
  17. @theintz
  18. @theintz
Commits on Mar 19, 2014
  1. @theintz
  2. @theintz
  3. @theintz
  4. @theintz
  5. @theintz
  6. @theintz
  7. @theintz

    added test for proper invocation of error handler when using multi me…

    theintz authored
    …thods on connection errors
  8. @theintz

    fix segfault which occurred when an error handler was set and unset a…

    theintz authored
    …gain (which was done automatically when setting a new error handler) and then called from outside.
    
    the reason why this happened is that the actual php object was unset unintentionally. the commit also contains a test to check for this behavior. i am unsure if the implementation is 100% correct, there may be a memory leak.
Commits on May 5, 2014
  1. @theintz
Commits on May 28, 2014
  1. @theintz
Commits on Jun 3, 2014
  1. @theintz
This page is out of date. Refresh to see the latest.
View
4 php_phpiredis.h
@@ -11,10 +11,14 @@
#define PHP_PHPIREDIS_VERSION "1.0.0"
#define PHP_PHPIREDIS_EXTNAME "phpiredis"
+#define PHPIREDIS_ERROR_CONNECTION 1
+#define PHPIREDIS_ERROR_PROTOCOL 2
+
PHP_MINIT_FUNCTION(phpiredis);
PHP_FUNCTION(phpiredis_connect);
PHP_FUNCTION(phpiredis_pconnect);
PHP_FUNCTION(phpiredis_disconnect);
+PHP_FUNCTION(phpiredis_set_error_handler);
PHP_FUNCTION(phpiredis_command_bs);
PHP_FUNCTION(phpiredis_command);
PHP_FUNCTION(phpiredis_multi_command);
View
3  php_phpiredis_struct.h
@@ -4,7 +4,8 @@ typedef struct _phpiredis_connection {
redisContext *c;
char* ip;
int port;
- zend_bool is_persistent;
+ zend_bool is_persistent;
+ void* error_callback;
} phpiredis_connection;
typedef struct _phpiredis_reader {
View
133 phpiredis.c
@@ -21,8 +21,25 @@ static
void convert_redis_to_php(phpiredis_reader* reader, zval* return_value, redisReply* reply TSRMLS_DC);
static
+void free_error_callback(phpiredis_connection *connection TSRMLS_DC)
+{
+ // calling this function during shutdown leads to a segfault, because the error handler has already
+ // been deallocated by the runtime engine.
+ if (connection->error_callback != NULL) {
+ // we must not free the function itself, because that deletes the actual PHP object,
+ // so we only decrease the reference counter
+ Z_DELREF_PP(&((callback*) connection->error_callback)->function);
+ efree(connection->error_callback);
+ connection->error_callback = NULL;
+ }
+}
+
+static
void s_destroy_connection(phpiredis_connection *connection TSRMLS_DC)
{
+ // we explicitly don't free the error callback during shutdown, because it is usually already deallocated
+ // this may create a memory leak, but I don't know how to properly deallocate the structure and since
+ // we are shutting down anyway, it's only a problem on a principle level
if (connection) {
pefree(connection->ip, connection->is_persistent);
if (connection->c != NULL) {
@@ -80,15 +97,53 @@ phpiredis_connection *s_create_connection (const char *ip, int port, zend_bool i
return NULL;
}
- connection = pemalloc(sizeof(phpiredis_connection), is_persistent);
- connection->c = c;
- connection->ip = pestrdup(ip, is_persistent);
- connection->port = port;
- connection->is_persistent = is_persistent;
+ connection = pemalloc(sizeof(phpiredis_connection), is_persistent);
+ connection->c = c;
+ connection->ip = pestrdup(ip, is_persistent);
+ connection->port = port;
+ connection->is_persistent = is_persistent;
+ connection->error_callback = NULL;
return connection;
}
+static
+int handle_error_callback(phpiredis_connection *connection, int type, char *msg, int len TSRMLS_DC) {
+ if (connection->error_callback == NULL) {
+ // raise PHP error if no handler is set
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", msg);
+ return SUCCESS;
+ }
+
+ zval *arg[2];
+ zval *return_value;
+ int retval = SUCCESS;
+
+ MAKE_STD_ZVAL(arg[0]);
+ ZVAL_LONG(arg[0], type);
+ MAKE_STD_ZVAL(arg[1]);
+
+ // only set second argument when msg is given
+ if (msg != NULL && len > 0) {
+ ZVAL_STRINGL(arg[1], msg, len, 1);
+ }
+
+ MAKE_STD_ZVAL(return_value);
+
+ if (call_user_function(EG(function_table), NULL, ((callback*) connection->error_callback)->function, return_value, 2, arg TSRMLS_CC) == FAILURE) {
+ // return FAILURE to signal something went wrong with the error handler
+ retval = FAILURE;
+ }
+
+ // TODO: we could also return whatever the error handler returned to allow for more flexibility
@seppo0010 Collaborator

Isn't this relatively easy to address?

@theintz
theintz added a note

@seppo0010 yes, it would be a simple change, but I didn't see a use case for that. we could implement it, if you think it's useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ zval_ptr_dtor(&return_value);
+ zval_ptr_dtor(&arg[0]);
+ zval_ptr_dtor(&arg[1]);
+
+ // return SUCCESS to signal successful execution of the error handler
+ return retval;
+}
+
PHP_FUNCTION(phpiredis_connect)
{
phpiredis_connection *connection;
@@ -133,6 +188,8 @@ PHP_FUNCTION(phpiredis_pconnect)
}
connection = (phpiredis_connection *) le->ptr;
+ // reset error handler, as it was cleaned up after the last request
+ connection->error_callback = NULL;
ZEND_REGISTER_RESOURCE(return_value, connection, le_redis_persistent_context);
efree(hashed_details);
@@ -177,6 +234,39 @@ PHP_FUNCTION(phpiredis_disconnect)
RETURN_TRUE;
}
+PHP_FUNCTION(phpiredis_set_error_handler)
+{
+ zval *ptr, **function;
+ phpiredis_connection *connection;
+ char *name;
+
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rZ", &ptr, &function) == FAILURE) {
+ return;
+ }
+
+ ZEND_FETCH_RESOURCE2(connection, phpiredis_connection *, &ptr, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context);
+
+ if ((*function)->type == IS_NULL) {
+ free_error_callback(connection TSRMLS_CC);
+ } else {
+ if (!zend_is_callable(*function, 0, &name TSRMLS_CC)) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument is not a valid callback");
+ efree(name);
+ RETURN_FALSE;
+ }
+
+ efree(name);
+ free_error_callback(connection TSRMLS_CC);
+
+ connection->error_callback = emalloc(sizeof(callback));
+
+ Z_ADDREF_PP(function);
+ ((callback*) connection->error_callback)->function = *function;
+ }
+
+ RETURN_TRUE;
+}
+
PHP_FUNCTION(phpiredis_multi_command)
{
zval **tmp;
@@ -220,12 +310,18 @@ PHP_FUNCTION(phpiredis_multi_command)
add_index_bool(return_value, i, 0);
}
+ handle_error_callback(connection, PHPIREDIS_ERROR_CONNECTION, connection->c->errstr, strlen(connection->c->errstr) TSRMLS_CC);
if (reply) freeReplyObject(reply);
- efree(result);
+ zval_ptr_dtor(result);
+
break;
}
+ if (reply->type == REDIS_REPLY_ERROR) {
+ handle_error_callback(connection, PHPIREDIS_ERROR_PROTOCOL, reply->str, reply->len TSRMLS_CC);
+ }
+
convert_redis_to_php(NULL, result, reply TSRMLS_CC);
add_index_zval(return_value, i, result);
freeReplyObject(reply);
@@ -309,12 +405,17 @@ PHP_FUNCTION(phpiredis_multi_command_bs)
add_index_bool(return_value, i, 0);
}
+ handle_error_callback(connection, PHPIREDIS_ERROR_CONNECTION, connection->c->errstr, strlen(connection->c->errstr) TSRMLS_CC);
if (reply) freeReplyObject(reply);
- efree(result);
+ zval_ptr_dtor(result);
break;
}
+ if (reply->type == REDIS_REPLY_ERROR) {
+ handle_error_callback(connection, PHPIREDIS_ERROR_PROTOCOL, reply->str, reply->len TSRMLS_CC);
+ }
+
convert_redis_to_php(NULL, result, reply TSRMLS_CC);
add_index_zval(return_value, i, result);
freeReplyObject(reply);
@@ -338,16 +439,16 @@ PHP_FUNCTION(phpiredis_command)
reply = redisCommand(connection->c, command);
if (reply == NULL) {
+ handle_error_callback(connection, PHPIREDIS_ERROR_CONNECTION, connection->c->errstr, strlen(connection->c->errstr) TSRMLS_CC);
+
RETURN_FALSE;
- return;
}
if (reply->type == REDIS_REPLY_ERROR) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, reply->str);
+ handle_error_callback(connection, PHPIREDIS_ERROR_PROTOCOL, reply->str, reply->len TSRMLS_CC);
freeReplyObject(reply);
RETURN_FALSE;
- return;
}
convert_redis_to_php(NULL, return_value, reply TSRMLS_CC);
@@ -420,19 +521,19 @@ PHP_FUNCTION(phpiredis_command_bs)
efree(argvlen);
if (redisGetReply(connection->c, &reply) != REDIS_OK) {
+ handle_error_callback(connection, PHPIREDIS_ERROR_CONNECTION, connection->c->errstr, strlen(connection->c->errstr) TSRMLS_CC);
+
// only free if the reply was actually created
if (reply) freeReplyObject(reply);
RETURN_FALSE;
- return;
}
if (reply->type == REDIS_REPLY_ERROR) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", reply->str);
+ handle_error_callback(connection, PHPIREDIS_ERROR_PROTOCOL, reply->str, reply->len TSRMLS_CC);
freeReplyObject(reply);
RETURN_FALSE;
- return;
}
convert_redis_to_php(NULL, return_value, reply TSRMLS_CC);
@@ -685,7 +786,6 @@ PHP_FUNCTION(phpiredis_reader_reset)
reader->reader = redisReplyReaderCreate();
}
-
PHP_FUNCTION(phpiredis_reader_destroy)
{
zval *ptr;
@@ -762,7 +862,6 @@ PHP_FUNCTION(phpiredis_reader_get_reply)
} else if (aux == NULL) {
RETURN_FALSE; // incomplete
}
-
}
convert_redis_to_php(reader, return_value, aux TSRMLS_CC);
@@ -825,6 +924,9 @@ PHP_MINIT_FUNCTION(phpiredis)
REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_STATUS", REDIS_REPLY_STATUS, CONST_PERSISTENT|CONST_CS);
REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_ERROR", REDIS_REPLY_ERROR, CONST_PERSISTENT|CONST_CS);
+ REGISTER_LONG_CONSTANT("PHPIREDIS_ERROR_CONNECTION", PHPIREDIS_ERROR_CONNECTION, CONST_PERSISTENT|CONST_CS);
+ REGISTER_LONG_CONSTANT("PHPIREDIS_ERROR_PROTOCOL", PHPIREDIS_ERROR_PROTOCOL, CONST_PERSISTENT|CONST_CS);
+
return SUCCESS;
}
@@ -832,6 +934,7 @@ static zend_function_entry phpiredis_functions[] = {
PHP_FE(phpiredis_connect, NULL)
PHP_FE(phpiredis_pconnect, NULL)
PHP_FE(phpiredis_disconnect, NULL)
+ PHP_FE(phpiredis_set_error_handler, NULL)
PHP_FE(phpiredis_command, NULL)
PHP_FE(phpiredis_command_bs, NULL)
PHP_FE(phpiredis_multi_command, NULL)
View
4 tests/008.phpt
@@ -12,6 +12,10 @@ if (!$link = my_phpiredis_connect($host))
printf("[001] Cannot connect to the server using host=%s\n",
$host);
+// since we are now raising php errors on connection errors, we have to set
+// an empty error handler here, otherwise the test breaks
+phpiredis_set_error_handler($link, function($a, $b){});
+
phpiredis_command($link, 'SET a 1');
var_dump(phpiredis_command($link, 'GET a'));
var_dump(phpiredis_command($link, 'QUIT'));
View
4 tests/009.phpt
@@ -12,6 +12,10 @@ if (!$link = my_phpiredis_connect($host))
printf("[001] Cannot connect to the server using host=%s\n",
$host);
+// since we are now raising php errors on connection errors, we have to set
+// an empty error handler here, otherwise the test breaks
+phpiredis_set_error_handler($link, function($a, $b){});
+
phpiredis_command($link, 'SET a 1');
var_dump(phpiredis_multi_command($link, array('GET a', 'QUIT', 'GET a')));
?>
View
108 tests/027.phpt
@@ -0,0 +1,108 @@
+--TEST--
+Test per-connection error handlers
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+if (!defined('PHPIREDIS_ERROR_CONNECTION') || !defined('PHPIREDIS_ERROR_PROTOCOL')) {
+ printf("Constants are not defined\n");
+}
+
+if (!function_exists('phpiredis_set_error_handler')) {
+ printf("Function is not defined\n");
+}
+
+$error_msg_signaled = '';
+$error_type_signaled = 0;
+
+// this error handler just sets the global variables
+$callback = function($type, $msg) {
+ printf("Error handler called!\n");
+
+ global $error_type_signaled, $error_msg_signaled;
+ $error_msg_signaled = $msg;
+ $error_type_signaled = $type;
+};
+
+$host = '127.0.0.1';
+$link = my_phpiredis_connect($host);
+if (!$link) {
+ printf("Error connecting to the server using host=%s\n", $host);
+}
+
+if (!phpiredis_set_error_handler($link, $callback)) {
+ printf("Error attaching error handler\n");
+}
+
+// do a faulty operation
+phpiredis_command($link, 'DEL test');
+phpiredis_command($link, 'SET test 1');
+phpiredis_command($link, 'LLEN test');
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if ($error_type_signaled != PHPIREDIS_ERROR_PROTOCOL) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled, PHPIREDIS_ERROR_PROTOCOL);
+}
+
+if (substr($error_msg_signaled, 0, 9) != 'WRONGTYPE') {
+ printf("Wrong error message returned, was %s, should have started with %s\n", $error_msg_signaled, 'WRONGTYPE');
+}
+
+// reset vars
+$error_msg_signaled = '';
+$error_type_signaled = 0;
+
+// test phpiredis_command_bs
+phpiredis_command_bs($link, array('LLEN', 'test'));
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if ($error_type_signaled != PHPIREDIS_ERROR_PROTOCOL) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled, PHPIREDIS_ERROR_PROTOCOL);
+}
+
+if (substr($error_msg_signaled, 0, 9) != 'WRONGTYPE') {
+ printf("Wrong error message returned, was %s, should have started with %s\n", $error_msg_signaled, 'WRONGTYPE');
+}
+
+// reset vars
+$error_msg_signaled = '';
+$error_type_signaled = 0;
+
+// remove error handler and check error was properly raised
+phpiredis_set_error_handler($link, null);
+
+@phpiredis_command($link, 'LLEN test');
+
+$error_last = error_get_last();
+if ($error_last == null) {
+ printf("No error was raised although the error handler was removed.\n");
+}
+
+// test calling phpiredis_set_error_handler with an invalid argument
+@phpiredis_set_error_handler($link, 0);
+
+$error_last = error_get_last();
+if ($error_last == null) {
+ printf("No error was raised although an invalid callback was passed.\n");
+}
+
+if (strpos($error_last['message'], "Argument is not a valid callback") === false) {
+ printf("Last error message is not properly passed.\n");
+}
+
+echo "OK" . PHP_EOL;
+?>
+--EXPECT--
+Error handler called!
+Error handler called!
+OK
View
75 tests/028.phpt
@@ -0,0 +1,75 @@
+--TEST--
+Test error handler on connection failure
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$error_msg_signaled = '';
+$error_type_signaled = 0;
+
+// this error handler just sets the global variables
+$callback = function($type, $msg) {
+ printf("Error handler called!\n");
+
+ global $error_type_signaled, $error_msg_signaled;
+ $error_msg_signaled = $msg;
+ $error_type_signaled = $type;
+};
+
+$host = '127.0.0.1';
+$link = my_phpiredis_connect($host);
+if (!$link) {
+ printf("Error connecting to the server using host=%s\n", $host);
+}
+
+if (!phpiredis_set_error_handler($link, $callback)) {
+ printf("Error attaching error handler\n");
+}
+
+// close the connection right away
+phpiredis_command($link, 'QUIT');
+phpiredis_command($link, 'GET test');
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if ($error_type_signaled != PHPIREDIS_ERROR_CONNECTION) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled, PHPIREDIS_ERROR_CONNECTION);
+}
+
+// reset vars
+$error_msg_signaled = '';
+$error_type_signaled = 0;
+
+// test phpiredis_command_bs
+phpiredis_command_bs($link, array('GET', 'test'));
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if ($error_type_signaled != PHPIREDIS_ERROR_CONNECTION) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled, PHPIREDIS_ERROR_CONNECTION);
+}
+
+// remove error handler and check error was properly raised
+phpiredis_set_error_handler($link, null);
+
+@phpiredis_command($link, 'GET test');
+
+$error_last = error_get_last();
+if ($error_last == null) {
+ printf("No error was raised although the error handler was removed.\n");
+}
+
+echo "OK" . PHP_EOL;
+?>
+--EXPECT--
+Error handler called!
+Error handler called!
+OK
View
120 tests/029.phpt
@@ -0,0 +1,120 @@
+--TEST--
+Test error handler for multi commands
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$error_msg_signaled = array();
+$error_type_signaled = array();
+
+// this error handler just sets the global variables
+$callback = function($type, $msg) {
+ printf("Error handler called!\n");
+
+ global $error_type_signaled, $error_msg_signaled;
+ $error_msg_signaled[] = $msg;
+ $error_type_signaled[] = $type;
+};
+
+$host = '127.0.0.1';
+$link = my_phpiredis_connect($host);
+if (!$link) {
+ printf("Error connecting to the server using host=%s\n", $host);
+}
+
+if (!phpiredis_set_error_handler($link, $callback)) {
+ printf("Error attaching error handler\n");
+}
+
+$commands = array(
+ 'DEL test',
+ 'SET test 2',
+ 'LLEN test',
+ 'GET test',
+ 'SMEMBERS test'
+);
+
+$expected_result = array(
+ 1,
+ 'OK',
+ 'WRONGTYPE Operation against a key holding the wrong kind of value',
+ 2,
+ 'WRONGTYPE Operation against a key holding the wrong kind of value'
+);
+
+$result = phpiredis_multi_command($link, $commands);
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if (count($error_msg_signaled) != 2 || count($error_type_signaled) != 2) {
+ printf("The wrong number of errors was raised: %d\n", count($error_msg_signaled));
+}
+
+if ($error_type_signaled[0] != PHPIREDIS_ERROR_PROTOCOL) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled[0], PHPIREDIS_ERROR_PROTOCOL);
+}
+
+if (substr($error_msg_signaled[0], 0, 9) != 'WRONGTYPE') {
+ printf("Wrong error message returned, was %s, should have started with %s\n", $error_msg_signaled[0], 'WRONGTYPE');
+}
+
+if (array_diff_assoc($result, $expected_result) !== array()) {
+ printf("The actual result does not match the expected result. Actual: %s\n", print_r($result, true));
+}
+
+// reset vars
+$error_msg_signaled = array();
+$error_type_signaled = array();
+
+// transform command array
+array_walk($commands, function(&$value, $index) {
+ $value = explode(' ', $value);
+});
+
+// test phpiredis_command_bs
+phpiredis_multi_command_bs($link, $commands);
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if (count($error_msg_signaled) != 2 || count($error_type_signaled) != 2) {
+ printf("The wrong number of errors was raised: %d\n", count($error_msg_signaled));
+}
+
+if ($error_type_signaled[0] != PHPIREDIS_ERROR_PROTOCOL) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled[0], PHPIREDIS_ERROR_PROTOCOL);
+}
+
+if (substr($error_msg_signaled[0], 0, 9) != 'WRONGTYPE') {
+ printf("Wrong error message returned, was %s, should have started with %s\n", $error_msg_signaled[0], 'WRONGTYPE');
+}
+
+if (array_diff_assoc($result, $expected_result) !== array()) {
+ printf("The actual result does not match the expected result. Actual: %s\n", print_r($result, true));
+}
+
+// remove error handler and check error was properly raised
+phpiredis_set_error_handler($link, null);
+
+@phpiredis_multi_command_bs($link, $commands);
+
+$error_last = error_get_last();
+if ($error_last == null) {
+ printf("No error was raised although the error handler was removed.\n");
+}
+
+echo "OK" . PHP_EOL;
+?>
+--EXPECT--
+Error handler called!
+Error handler called!
+Error handler called!
+Error handler called!
+OK
View
64 tests/030.phpt
@@ -0,0 +1,64 @@
+--TEST--
+Test error handler on connection failure with multi method
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$error_msg_signaled = array();
+$error_type_signaled = array();
+
+// this error handler just sets the global variables
+$callback = function($type, $msg) {
+ printf("Error handler called!\n");
+
+ global $error_type_signaled, $error_msg_signaled;
+ $error_msg_signaled[] = $msg;
+ $error_type_signaled[] = $type;
+};
+
+$host = '127.0.0.1';
+$link = my_phpiredis_connect($host);
+if (!$link) {
+ printf("Error connecting to the server using host=%s\n", $host);
+}
+
+if (!phpiredis_set_error_handler($link, $callback)) {
+ printf("Error attaching error handler\n");
+}
+
+$commands = array(
+ 'QUIT',
+ 'GET test'
+);
+
+$expected_result = array(
+ 'OK',
+ false
+);
+
+$result = phpiredis_multi_command($link, $commands);
+
+$error_last = error_get_last();
+if ($error_last != null) {
+ printf("A php error was raised although a handler was set: %s\n", print_r($error_last, true));
+}
+
+if (count($error_msg_signaled) != 1 || count($error_type_signaled) != 1) {
+ printf("The wrong number of errors was raised: %d\n", count($error_msg_signaled));
+}
+
+if ($error_type_signaled[0] != PHPIREDIS_ERROR_CONNECTION) {
+ printf("Wrong error type returned, was %d, should have been %d\n", $error_type_signaled[0], PHPIREDIS_ERROR_CONNECTION);
+}
+
+if (array_diff_assoc($result, $expected_result) !== array()) {
+ printf("The actual result does not match the expected result. Actual: %s\n", print_r($result, true));
+}
+
+echo "OK" . PHP_EOL;
+?>
+--EXPECT--
+Error handler called!
+OK
View
49 tests/031.phpt
@@ -0,0 +1,49 @@
+--TEST--
+Test that no segfault occurs when attaching and removing an error handler
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$mem = 0;
+$callback = function($type, $msg) {
+ printf("Error handler called!\n");
+};
+
+$host = '127.0.0.1';
+$link = my_phpiredis_connect($host);
+if (!$link) {
+ printf("Error connecting to the server using host=%s\n", $host);
+}
+
+if (!phpiredis_set_error_handler($link, $callback)) {
+ printf("Error attaching error handler\n");
+}
+
+// here we should still be able to call it
+$callback(1, 'test');
+
+// now we remove it but have to make sure it still exists
+phpiredis_set_error_handler($link, null);
+
+$callback(1, 'test');
+
+// check for leaking memory
+$mem = memory_get_usage() - $mem;
+
+phpiredis_set_error_handler($link, $callback);
+phpiredis_set_error_handler($link, null);
+
+$mem = memory_get_usage() - $mem;
+
+if (abs($mem) != 0) {
+ printf("Memory footprint was: %d\n", $mem);
+}
+
+echo "OK" . PHP_EOL;
+?>
+--EXPECT--
+Error handler called!
+Error handler called!
+OK
Something went wrong with that request. Please try again.