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

add invokelatest to circumvent world-age problems #19784

Merged
merged 9 commits into from
Apr 27, 2017

Conversation

stevengj
Copy link
Member

In cfunction callbacks (#19774) and functions that call eval or similar (#19770), one occasionally runs into problems where an obsolete version of a function is called when the latest one is desired. Currently, the workaround is to call eval, which is somewhat awkward and error-prone due to the necessity of constructing an Expr. This PR adds an invokelatest(f, args...) function that calls the latest version of f(args...).

@stevengj
Copy link
Member Author

(I modeled the jl_invoke_latest function somewhat after the jl_call API functions, which also temporarily reset the world age. One thing that confused me: I would have expected that the restoration world_age = last_age of the original value would have to go into some kind of "finally" clause so that it is still executed when exceptions occur, but jl_call does not do this. Does the exception-handling process automatically reset the world age?)

@@ -584,18 +584,18 @@ function uv_getaddrinfocb(req::Ptr{Void}, status::Cint, addrinfo::Ptr{Void})
cb = unsafe_pointer_to_objref(data)::Function
pop!(callback_dict,cb) # using pop forces an error if cb not in callback_dict
if status != 0 || addrinfo == C_NULL
cb(UVError("uv_getaddrinfocb received an unexpected status code", status))
invokelatest(cb, UVError("uv_getaddrinfocb received an unexpected status code", status))
Copy link
Member

Choose a reason for hiding this comment

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

our libuv callbacks should never be invoking callbacks directly. we've refactored most of them, but it looks like this one still needs to be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, but clearly a matter for a different PR...

Copy link
Contributor

@yuyichao yuyichao left a comment

Choose a reason for hiding this comment

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

LGTM other than the minor issues commented above.

I also think it's useful to have a version of cfunction that doesn't capture the world (and therefore might do conditional dynamic dispatch).

src/gf.c Outdated
argv[0] = f;
for(int i=1; i<nargs+1; i++)
argv[i] = jl_fieldref(argtuple, i-1);
size_t last_age = jl_get_ptls_states()->world_age;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be a jl_ptls_t ptls = jl_get_ptls_states(); at the beginning.

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you fixed another one....

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I fixed the fix

src/julia.h Outdated
@@ -1381,6 +1381,7 @@ STATIC_INLINE int jl_vinfo_usedundef(uint8_t vi)

JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs);
JL_DLLEXPORT jl_value_t *jl_invoke(jl_method_instance_t *meth, jl_value_t **args, uint32_t nargs);
JL_DLLEXPORT jl_value_t *jl_invoke_latest(jl_value_t *f, jl_value_t *argtuple);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need to make this a C api? I think we can leave it out so that changing the implementation (likely) won't be a breaking change at all.

@stevengj
Copy link
Member Author

stevengj commented Dec 31, 2016

I guess Compat for this will be as simple as invokelatest(f, args...) = eval(Expr(:call, f, args...)), though that won't work if one of the args is a Symbol or Expr. Maybe invokelatest(f, args...) = eval(Expr(:call, QuoteNode(f), map(QuoteNode,args)...)).

@stevengj
Copy link
Member Author

Can we get this in before the feature freeze?

vtjnash
vtjnash previously requested changes Jan 1, 2017
Copy link
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

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

jl_f__apply_pure would be a better model for how to implement this. But I'm not sure I like this function (that's why I didn't include it in the PR and went with the more annoying eval call), or will go with a different way entirely for making cfunction work. We'll definitely put something in before the feature freeze though.

If we do go with this, it should have a not-pure guard at the top (

jl_error("task switch not allowed from inside staged nor pure functions");
)

@stevengj
Copy link
Member Author

stevengj commented Jan 2, 2017

I did look at jl_f__apply_pure in looking at how to extract the tuple elements. The main difference seems to be that jl_f__apply handles argument containers besides tuples (e.g. arrays), which didn't seem worth it here. But I'll change it to use jl_f__apply if you prefer.

@stevengj
Copy link
Member Author

stevengj commented Jan 2, 2017

(Even if there are better ways to do cfunction, I still think it would be good to have an invokelatest function for easy fixes when world-age problems arise.)

@stevengj
Copy link
Member Author

stevengj commented Jan 4, 2017

@vtjnash, does it look good now, aside from apparently unrelated Travis problems?

@stevengj
Copy link
Member Author

stevengj commented Jan 5, 2017

Rebased. Good to merge?

@ararslan
Copy link
Member

Bump, @vtjnash

@vtjnash
Copy link
Member

vtjnash commented Jan 11, 2017

I think I want to go a different direction with this (https://discourse.julialang.org/t/proposal-for-a-first-class-dispatch-wrapper/1127/2?u=jameson), which is why I've been slow here.

@stevengj
Copy link
Member Author

stevengj commented Feb 7, 2017

Rebased. @vtjnash, are you okay with this PR now (cf. #20497)?

@stevengj
Copy link
Member Author

Ping @vtjnash.

@JeffBezanson
Copy link
Member

Bump. It seems we should merge this, since otherwise people will just define

invokelatest(f, x) = eval(current_module(), Expr(:body, Expr(:return, Expr(:call, f, QuoteNode(x)))))

which doesn't seem better.

@stevengj
Copy link
Member Author

Rebased.

@vtjnash
Copy link
Member

vtjnash commented Apr 25, 2017

I'm just bike-shedding, but should we call this applylatest instead? (since it uses the API of apply, not invoke)

@JeffBezanson
Copy link
Member

It takes separate arguments, not a container, so it is invoke-like.

@ararslan
Copy link
Member

Can this still make it into 0.6? Kind of late now, but it's been in the works since before the planned feature freeze. (cc @tkelman)

@JeffBezanson
Copy link
Member

I suddenly became very interested in this when I realized what huge overhead making and eval'ing expressions is adding to remote operations.

@amitmurthy
Copy link
Contributor

Will this be applicable here - https://github.com/JuliaString/Format.jl/blob/newmaster/src/cformat.jl#L6 - too? If so, an update of this section - https://docs.julialang.org/en/latest/manual/methods.html#Redefining-Methods-1 - stating performance implications of a second eval and a fix via invokelatest would be helpful, else folks are going to end up doing a second eval anyway.

@JeffBezanson JeffBezanson added this to the 0.6.0 milestone Apr 26, 2017
@JeffBezanson
Copy link
Member

Yes looks like it would also be applicable there. I'm not sure how much we want to commit to this function, but I guess it would be ok to mention in the docs.

@tkelman
Copy link
Contributor

tkelman commented Apr 26, 2017

Are we sure we want it exported?

@stevengj
Copy link
Member Author

stevengj commented Apr 26, 2017

I can change it to a non-exported function and remove it from NEWS if that is what people prefer for 0.6.

@StefanKarpinski
Copy link
Member

StefanKarpinski commented Apr 26, 2017

I think @JeffBezanson and @vtjnash's call, but it seems like an option.

@JeffBezanson
Copy link
Member

I'd rather not export it (especially at this point in the release cycle).

@vtjnash
Copy link
Member

vtjnash commented Apr 26, 2017

I would keep the NEWS entry

src/builtins.c Outdated
{
jl_ptls_t ptls = jl_get_ptls_states();
size_t last_age = ptls->world_age;
ptls->world_age = jl_world_counter;
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a if (!ptls->in_pure_callback) guard on this line?

Copy link
Member Author

Choose a reason for hiding this comment

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

You mean like

    if (ptls->in_pure_callback)
        jl_error("invokelatest cannot be used in a generated or pure functions");

similar to jl_toplevel_eval_in or jl_switchto?

Copy link
Member

Choose a reason for hiding this comment

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

No error is necessary in this case. It just should ignore jl_world_counter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, done, though I don't really understand why this is the case.

Copy link
Member

Choose a reason for hiding this comment

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

It's mostly just by-definition. A pure function is one for which the world-counter has no effect. In those other examples, those two functions try to change jl_world_counter, so they have to throw an error. In this example, it's only trying to read jl_world_counter, which is OK, as long as it can't see the value change.


Calls `f(args...)`, but guarantees that the most recent method of `f`
will be executed. This is useful in specialized circumstances,
especially `cfunction` callbacks that may extract a Julia `Function`
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't need to mention cfunction anymore, it's not relevant there anymore. But perhaps should add a mention of long-running event loops?

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking of something like JuliaGizmos/Reactive.jl#131, where it's not calling eval, but it wants to be able to react when other Tasks call eval.

@stevengj
Copy link
Member Author

stevengj commented Apr 26, 2017

Removed the export, updated the docstring, and added an in_pure_callback check.

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.

8 participants