tokio-rs / tokio-core

I/O primitives and event loop for async I/O in Rust
Apache License 2.0
638 stars 115 forks source link

cannot recursively call into Core #319

Open vorner opened 6 years ago

vorner commented 6 years ago

Hello

It seems that by using tokio-executor under the hood, the behaviour changed in backwards-incompatible way. If inside one future I create a new core and try to run something in it, it panics with cannot recursively call into Core (https://github.com/tokio-rs/tokio-core/blob/master/src/reactor/mod.rs#L242).

I know this is an edge case and that I'd try to avoid it in a real application (I use it in some tests, checking some edge cases of my own) and that the reason for it is to actually disallow this degenerate case, but it was allowed before, so I don't know if it's OK to forbid it now.

carllerche commented 6 years ago

This behavior was, in theory, never permitted. It was just never enforced.

Could you explain more how you were using it? It would probably be best to figure out a way around it.

vorner commented 6 years ago

As I said, it's a test, this one: https://github.com/vorner/corona/blob/master/test/recursive_core.rs. I put some core-related things into TLS, and I wanted to check I don't mix it up if there are multiple and take turns or something. I can probably just throw the test out, when it comes to that. I'm just waiting for the waters to calm down before I port the whole library onto the new tokio anyway, so it won't be relevant any more.

My point was more about this might be an oversight or something like that. Anyway, if it acts like it should, it probably should be documented in the Panics section.

bluejekyll commented 6 years ago

If I’m understanding this properly, doesn’t this meant that two independent libraries can’t create Cores separately in the case where one might depend on the next?

Especially in the case of wrappers that might be attempting to create synchronous interfaces?

Do we need to enforce that a Core is the only Core on any particular thread?

vorner commented 6 years ago

My understanding is you can run one core, let it terminate, then run another. But if you create one core, spawn a future into it and inside that future create another core and run it, it'll panic because you most likely don't want that. The inner core would be blocking inside the outer one and the outer one would not be able to execute futures at the time.

And in my understanding libraries should never create their own cores, but be provided with a handle (or tokio::runtime) by the application and spawn whatever they need onto that.

carllerche commented 6 years ago

@bluejekyll as @vorner stated above, you can create an arbitrary number of cores per thread. What you cannot do is block on a core (call run) from within a future as that would be pretty disasterous to the Async runtime characteristics.

bluejekyll commented 6 years ago

Is this safe?

https://github.com/bluejekyll/trust-dns/blob/master/resolver/src/resolver.rs#L126

Also, I know it’s not efficient, and I’ve planned to change it, but I’m concerned it’s not safe at the moment.

cetra3 commented 6 years ago

I'm hitting this issue with two libraries that manage their own cores (actix-web & ldap3). This is definitely a breaking change for me.

A few questions:

carllerche commented 6 years ago

Ah yes, that is unfortunate. However, nesting calls to Core::run is just not supported at this point. It never really was, it just was not enforced. The plan has always been to enforce it at some point.

I would rather dig in and figure out why you need to nest calls to Core::run, perhaps there is another way to go about doing it.

seanmonstar commented 6 years ago

If there is recursive cores then the inner one will block yes, but it should still work fine otherwise [...]

Actually, there are several cases where it will grind the thread to a halt. I've seen this reported before in hyper, where people have a Core running their server, and wish to start a client to pipe a request body to another location. They end up with core.run(client.request(req_using_server_body)), and so, the inner Core will block waiting for the incoming server body to make progress, but it cannot since the thread is blocked by the inner Core. Panicking is probably better here.

cetra3 commented 6 years ago

I agree in my case it'd probably make sense to make the trait I was working with futures-aware, but that will take some time to refactor. I have basically just spawned a separate thread to work around it for the time being.

I still was initially horrified over the panic, as I was not expecting it to happen at all! It would be nice if there was a Result returned instead, so I can deal with this at compile time, not when it popped up during runtime. I understand that'd probably break the signature of run though, so may not be possible.

At the bare minimum, maybe some documentation along the lines of: Calling this method when inside an existing future's execution (i.e, recursively) on the same thread will cause a panic, as it would otherwise cause a deadlock

carllerche commented 6 years ago

Yeah, we cannot break the signature of run. The current signature is unfortunate.

Would you mind submitting a PR that includes the snippet under the "panics" section of the docs?

cetra3 commented 6 years ago

Ok, I will submit a PR