Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redis from mruby #1152

Merged
merged 53 commits into from Mar 7, 2018
Merged

redis from mruby #1152

merged 53 commits into from Mar 7, 2018

Conversation

i110
Copy link
Contributor

@i110 i110 commented Dec 27, 2016

This PR makes it able to access redis from mruby hanlder, like the following:

        mruby.handler: |
          redis = H2O::Redis.new(:host => '127.0.0.1', :port => 6379)
          Proc.new do |env|
            set = redis.set('key', 'value')
            get = redis.get('key')
            # redis.disconnect
            [ 200, {}, [set.join, get.join] ]
          end

TODOs:

  • support more redis features (other commands, authentication, etc..)
  • consider preparing specific exception class for IO & procotol errors
  • embed mruby source code in handler/mruby/redis.c ? (I feel it'll reduce maintainability, so let's discuss)
  • timeout
  • subscribe

@yannick
Copy link
Contributor

yannick commented Jan 17, 2017

if you forget to call .join on the redis answer it segfaults (at least under macOS)

@i110 i110 changed the title redis from mruby [WIP] redis from mruby Jan 26, 2017
@i110
Copy link
Contributor Author

i110 commented Jan 26, 2017

@yannick sorry for my late reply, and thank you for noticing me that problem. This is still WIP, and after This PR gets merged, I'll fix it and address FIXMEs.

@yannick yannick mentioned this pull request Mar 17, 2017
@yannick
Copy link
Contributor

yannick commented Mar 23, 2017

@redis.connect
  	paste = @redis.call(:hget, "myhash", "field1").join

works fine. however there is a bug when multiple values are sent back from redis:

       @redis.connect
  	paste = @redis.call(:hgetall, "myhash").join

crashes

Assertion failed: (mrb_array_p(*parent)), function try_parentize, file /lib/handler/mruby/redis.c, line 257.

also, is there any reason the connect block is not inside .call?

@i110
Copy link
Contributor Author

i110 commented Mar 27, 2017

@yannick thank you for notifying me that. I fixed that bug in b100dd5

also, is there any reason the connect block is not inside .call?

no there were no reasons (if I recall correctly), so I moved ensure_connected into call method by 9c6034c

@yannick
Copy link
Contributor

yannick commented Jun 9, 2017

@i110 could you merge master into here, there are a few conflicts i'm unsure about
(also in regards of compat with #1173 )

@yannick
Copy link
Contributor

yannick commented Oct 18, 2017

there might still be a leak:
valgrind reports:
first a empty mruby handler:
https://gist.github.com/yannick/6f957847bb7fa6d88bca12fd1b792d7d
then a handler that calls redis.evalsha( "hash").join):
https://gist.github.com/yannick/e5436edc5aefca1f18510cded573d422

@i110
Copy link
Contributor Author

i110 commented Oct 20, 2017

Thank you @yannick, I fixed that problem at 70132d8 !

Copy link
Member

@kazuho kazuho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on the PR.

I have left my thoughts, mostly on the changes in the C side. To me it seems that several refactors have been made to the redis binding. By asking questions, I am trying to understand why the changes are necessary (or what the benefits are).

@@ -117,12 +119,14 @@ typedef struct st_h2o_mruby_generator_t {
} refs;
} h2o_mruby_generator_t;

#define H2O_MRUBY_CALLBACK_ID_NOOP 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if 0 as a callback ID is allowed. See https://github.com/h2o/h2o/blob/5b71e0a/lib/handler/mruby.c#L773.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In current implementation, 0 is actually not meaningful and I believe there're no users who actively uses it because it only raises runtime error which means programming error. OTOH we need a way to exit h2o_mruby_run_fiber

return;
}

if (command->type == H2O_REDIS_COMMAND_TYPE_MONITOR) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have code for "monitor" assuming that we always return a error?

return;

int err = H2O_REDIS_ERROR_NONE;
char *errstr = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need err and errstr?

I would appreciate it if you could merge them instead of having two. One way would be to only use const char * and have specific pointer values defined in the header file. Please consult http1client for details.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At 3ead0da I remoted int err, but still I'm feeling that it may be necessary to report better error message to the client, regarding the fact that error message may came from strerror

h2o_timeout_unlink(entry);

conn->_did_connect_timeout = 1;
invoke_deferred(conn, &conn->_defer_timeout_entry, on_connect_timeout_deferred);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would apppreciate if you could rather not make deferred calls within timeout callbacks. I believe that there is no reason to do so. Doing more things at once is generally better for performance (better locality) and debugging (gives us better trace).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that there is no reason to do so.

At that time there're some reasons to do workaround with hiredis async API, but at 3ead0da I refactored to eliminate such tricks

}
}

static void on_disconnect(const redisAsyncContext *redis, int status)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the introduction of the function mean that we now have a "disconnecting" (or "closing") state?

If yes, is there a need to add the state to h2o_redis_connection_state_t? If no, why do we need this function?

I would also appreciate it if you could consider unifying the term being used. At the moment I see "close" and "disconnect".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the introduction of the function mean that we now have a "disconnecting" (or "closing") state?

In our design, disconnecting hiredis object cannot be touched because once disconnecting procedure is fired our redis client immediately detach hiredis object and try to create new connection if another request comes. Regarding that, I don't think we have to have disconnecting state.

{
h2o_redis_conn_t *conn = H2O_STRUCT_FROM_MEMBER(h2o_redis_conn_t, _defer_timeout_entry, entry);

assert((conn->_redis->c.flags & REDIS_CONNECTED) == 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I correct in assuming that there is a value that represents the connection state in the redisAsyncContext? If so, can we use the value instead of having h2o_redis_connection_state_t?

Having two states that represents the same thing is a source of confusion.

Copy link
Contributor Author

@i110 i110 Jan 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a value that represents the connection state in the redisAsyncContext?

Yes, but it's a bit redundant and seems to be not intended to be used directly from outside of hiredis. And also considering that we have a bit different state model described in #1152 (comment), I think we should have our own state representation.

@yannick
Copy link
Contributor

yannick commented Jan 22, 2018

it would be great if there would be a way to re-initialize the redis connector if it got killed
or at least we need some example on how this should be handled.

afair: tearing up the redis connection outside of the request handler proc leaves the handler in a non working state if e.g. redis is restarted.

Copy link
Member

@kazuho kazuho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the changes. I have left my tentative thoughts. Please let me know what you think.

if (h2o_timeout_is_linked(&conn->_connect_timeout_entry))
h2o_timeout_unlink(&conn->_connect_timeout_entry);
if (h2o_linklist_is_linked(&conn->_connect_timeout._link))
h2o_timeout_dispose(conn->loop, &conn->_connect_timeout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hasn't _connect_timeout been initialized regardless of if it's has something linked?

In other words, I wonder if it is necessary have a surrounding if when calling the dispose function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conn->_connect_timeout may be uninitialized if the user set conn->connect_timeout to zero. so we have to check it by h2o_linklist_is_linked(&conn->_connect_timeout._link)

@@ -224,7 +224,7 @@ static h2o_iovec_t build_redis_value(h2o_iovec_t session_data)

#undef BASE64_LENGTH

static void redis_resumption_on_get(redisReply *reply, void *_accept_data)
static void redis_resumption_on_get(redisReply *reply, void *_accept_data, const char *errstr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we can receive a timeout error through errstr, is it possible to get rid of the timeout handling logic specific to redis-based session resumption that uses on_redis_resumption_get_failed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall correctly, on_redis_resumption_get_failed is introduced with zero timeout because h2o_socket_ssl_resume_server_handshake or something like that is not reentrant.

static void on_gc_dispose_redis(mrb_state *mrb, void *_conn)
{
struct st_h2o_mruby_redis_conn_t *conn = _conn;
if (conn == NULL) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance that conn becomes NULL? Unless there's such case, this early return is redundant with the potential of hiding a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed at c3d0f85

static void on_gc_dispose_command(mrb_state *mrb, void *_ctx)
{
struct st_h2o_mruby_redis_command_context_t *ctx = _ctx;
if (ctx == NULL) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed at c3d0f85

h2o_timeout_entry_t _timeout_entry;
h2o_timeout_entry_t _defer_timeout_entry;
h2o_timeout_t _connect_timeout;
h2o_timeout_entry_t _connect_timeout_entry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate the lifecycle of h2o_redis_conn_t::_redis?

I expected that it is set to non-NULL when redisAsyncConnect is called, and is set to NULL when redisAsyncFree is being called.

But that does not seem to be the case. For example, I do not see redisAsyncFree called when on_connect with a status code other than REDIS_OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically the lifecycle of redis object is managed by hiredis internal. For example, if on_connect gets called with REDIS_ERR, immediately after it hiredis will call __redisAsyncDisconnect which will finally dispose redis object.
https://github.com/h2o/h2o/blob/master/deps/hiredis/async.c#L503-L504

@i110
Copy link
Contributor Author

i110 commented Jan 29, 2018

@yannick
You can check whether the connection is still alive or not using redis.disconnected? Is this what you need?
OTOH connect method internally checks it, and all redis-command method (like get) calls connect method before emitting commands. So I think the users don't need to use disconnect? method in almost cases.

@yannick
Copy link
Contributor

yannick commented Jan 29, 2018

@i110 ah right, i forgot that it autoconnects, my bad sorry.

@proyb6 proyb6 mentioned this pull request Mar 4, 2018
@kazuho kazuho merged commit 1a99afd into master Mar 7, 2018
@kazuho
Copy link
Member

kazuho commented Mar 7, 2018

Thank you for your hard work and your patience!

@utrenkner utrenkner mentioned this pull request Oct 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants