-
Notifications
You must be signed in to change notification settings - Fork 824
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
mruby sleep #1330
mruby sleep #1330
Conversation
There was a problem hiding this 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 PR. Left my comments in-line. Please let me know what you think.
lib/handler/mruby/sleep.c
Outdated
} else { | ||
*run_again = 1; | ||
return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "argument must be either Fixnum or Float"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that ruby methods are expected to convert the input to appropriate type rather than raising an error upon seeing an unexpected type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CRuby converts the argument if and only if it responds to divmod
(see: https://github.com/ruby/ruby/blob/trunk/time.c#L2266), so for example, if we pass a String to sleep function, it raises a TypeError
without any conversion.
Then, regarding the compability with CRuby, it may be better to convert the argument using divmod
like CRuby does, but the conversion logic is a bit complicated, and I guess that it's very rare case to pass custom object to sleep function.
So, at this time, I feel restricting the argument type is a reasonable choice.
There was a problem hiding this 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 clarification. I did not know that the sleep
method of cruby depends on the existence of divmod
.
OTOH, I still believe that we should follow the principle of duck typing rather than checking the types. Can we simply call to_f
and then use the returned value instead of doing a switch-case?
lib/handler/mruby/sleep.c
Outdated
ctx->ctx = mctx; | ||
ctx->receiver = receiver; | ||
h2o_timeout_init(ctx->ctx->shared->ctx->loop, &ctx->timeout, msec); | ||
ctx->timeout_entry = (h2o_timeout_entry_t){0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason to zero-clear ctx->timeout_entry
considering that we are doing memset
?
|
||
/* defer freeing to avoid concurrent modification onf timeout linklist */ | ||
entry->cb = on_deferred_timeout; | ||
h2o_timeout_link(shared->ctx->loop, &shared->ctx->zero_timeout, entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to defer the disposal of h2o_timeout_t
?
Is it due to how h2o_timeout_run
is implemented? If that is the case, I think that we should address the issue by fixing h2o_timeout_run
(though if that seems like difficult, I am not against doing that after merging this PR as-is).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we have to defer is rather in h2o_evloop_run.
If we dispose h2o_timeout_t in timeout callback function, the linklist enumaration doesn't work anymore (because node = node->next
causes badaccess error)
Do you have any idea to address this issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we should resolve the issue after merging the PR using the current approach.
Basically what you need to do is record the address of h2o_timeout_t
that is being processed by h2o_evloop_run
as a member of h2o_evloop_t
, and if the address matches that of the disposed timeout, then update the recorded address so that it would point to the previous object. However implementing this correctly might require some work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mostly understand the approach you said. OTOH, I want to clarify the disadvantage of the current approach. Are you worried about the effect to the performance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't be worried about the impact of retaining one pointer per every call to h2o_timeout_run
and referring to the value in every call to h2o_timeout_dispose
, since we call h2o_timeout_run
only a few times per once a event loop, and since it is rare to call h2o_timeout_dispose
.
If the cost of either of the two becomes an issue, I think that we need to change the architecture of how we maintain the structures, such by changing it from a linked list to a red-black tree ordered by when the timeout events to be triggered, etc.
Anyways, I'll merge this PR as-is. I do not want to delay the introduction of sleep
by discussing how we should refactor our timeout design.
@kazuho Thank you for your comments. I addressed one and replied to the others. |
@kazuho I fixed a issue and added tests (I forgot to git add) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If performance is a concern, you could do something like:
if (type_is_int) {
fetch_int;
} else {
if (!type_is_float) {
invoke(`to_f`)
if (!type_is_float)
raise_error;
}
fetch_float;
}
lib/handler/mruby/sleep.c
Outdated
if (! mrb_respond_to(mrb, arg_sec, mrb_intern_lit(mrb, "to_f"))) { | ||
*run_again = 1; | ||
return mrb_exc_new_str_lit(mrb, E_ARGUMENT_ERROR, "the argument of the sleep function must respond to 'to_f' method"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to call mrb_respond_to
? I thought that mrb_funcall
will return an method invocation failure (that we can propagate to the caller) in case the object fails to respond to the method being invoked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kazuho Yes, but I thought the users never know that the h2o core calls to_f internally, so
the error message might be very confusing. It's better to show some good error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I would argue that you can spend time me to emit an error message that would satisfy the needs of any programmer in case a error occurrs, and doing so will not hurt the performance of the successful path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get your point. I just wanted to show kind error message to the users, and I'm not worried about performance here.
without respond_to check:
[h2o_mruby] in request:/:mruby raised: NoMethodError: undefined method 'to_f' for #<Object:0x102831620>
with the check:
[h2o_mruby] in request:/:mruby raised: (eval):10: the argument of the sleep function must respond to 'to_f' method (ArgumentError)
I feel the latter is more like an error message that would satisfy the needs of any programmer
you said. isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is that if you want to emit a friendly error, you can do the following:
- call
to_f
- if the invocation ended up in an exception, check if the error is a
NoMethodError
and then adjust the message of the exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I understand. okok!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add
sleep
function to the Kernel.What drove me to implement this is mainly H2O::Redis reconnect loop, but this can be useful for various situations as pointed out in #1283
NOTE: This PR is toward #1173.