-
Notifications
You must be signed in to change notification settings - Fork 7.6k
A standard for continuation-local variables? #2885
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
Comments
Count me as and interested party in this. We're also switching a lot between Finagle, at service in/out end points, and Observable business logic in the middle. We've been meaning to start using Zipkin for request tracking and we suspected that this might become an issue. |
Scala's
Perhaps @viktorklang / @richdougherty might be able to chime in with their experience from Akka / Play. |
With Akka, it could be hairy - since it would be natural to propagate such data between actors across services, and Akka has location transparency. Given that you store a trace ID in a continuation local (or whatever to call it), and you also store a Trace service (like in the Zipkin case), which is the implementation of the transport (how to store the trace ID to e.g. Cassandra later on). You probably wouldn't want to propagate the service implementation across actors that run on different machines, while it would be natural to propagate it within the same application - which is how Zipkin does it. |
+1 |
Yes, this is what happens with many of the concurrency logic we hide behind schedulers and operators, but it is generally painful. In addition, I think such construct poses considerable overhead for every task scheduled and since it is opt-in, there is no way to optimize against it. The RxJava way is to use composite objects and pass along any relevant state in the stream. Besides, I'd say that local-passing works in Akka because it is a framework: the framework is in control when you are in or out of an actor and as such, can manage the lifecycle and pass-around of the local state for you. RxJava, however, is a library which can't impose such constraints. |
Well, as you said, it would be opt-in. Every time you make a new continuation-local variable, we record the fact that there exists at least one continuation-local. If there is at least one, then we transfer all continuation-local variables' state to the new thread. If there are no continuation-locals, we noop (so there is no overhead).
You're missing a key point here - the reference to the continuation-local must be implicit, outside of the traditional scope where you explicitly pass around references. If you were to pass around trace data explicitly, that would mean you'd have to rewrite your entire application's internal (and external?) API to support tracing. An important feature of tracing systems is that they should be transparent and not intrusive in userland code. (That's one of the things ThreadLocals are made for in the first place, what we're after is a kind of improved ThreadLocal) |
Can't there be a way to attach an EventListener, which gets triggered every time a thread is "assigned"?Therefore we will configure it only once for each chain, ThreadLocals will be bound & removed when only appropriate. I'm pretty new to the library, so I'm aware I might be oversimplifying. |
You can use RxJavaSchedulersHook to achieve this already, but you'll have to provide your own continuation local (in this example it's Twitter's), and you'll have to run your application with a system property for it to work: package com.example
final class Hook extends RxJavaSchedulersHook {
import com.twitter.util.Local
override def onSchedule(action: Action0) : Action0 = {
val ctx = Local.save()
new Action0 {
override def call() {
Local.let(ctx) {
action.call()
}
}
}
}
} This is a Scala snippet that transfers Twitter's Locals between threads in RxJava, but you'll have to run your app with the property
or you can also configure the plugin with Java, but then you'll have to add code snippets somewhere in your application on startup. The ideal situation would be that it could just work out of the box. A compromise maybe, would be to let rxjava read classpath:rxjava-plugins.properties (if it exists) and load plugins specified there? It could also be implemented with a ServiceLoader. That way, we could add an optional jar to the classpath that added continuation-local support. (Though I would still say the best option is to provide continuation-locals as a core feature, independent of scheduler hooks) |
@eirslett Thanks for the direction! I found what I wanted. |
Joining the party late but I have something that might be of interest to you. So the continuation-local variable would be accessible to a sequence of observable onNexts. For instance if an observable emitted I have a proof of concept project that introduces a Observable<Integer> nums = Observable.range(0,10);
BiObservable<Integer, String> vs = BiObservable.generate(nums, Object::toString);
BiObservable<String[], String> arr = vs.map1((n) -> { return new String[n]; })
.doOnNext((a, s) -> {
for (int i = 0; i < a.length; i++)
a[i] = s;
}).subscribe(System.out::println); The operators for BiObservable generally can take a Here is a link to the proof of concept project. I would greatly appreciate any feedback or ideas. |
I did a schedulers blog post with an example that did automatic context copying on the scheduling boundary. As for the BiObserver, I'm quite interested how you implemented observeOn and the other queueing operators. |
I have not implemented observeOn yet. I think I could build that out once I merge it into RxJava. But my initial impression is that the existing Thanks, that's great feedback @akarnokd. |
I don't think there is a way to make the BiNotification very light. To avoid an allowaction of a tuple on each onNext I think you would need two NotificationLite & and a BiRingBuffer. |
Cross posting here for interested folks |
As per this earlier comment, we use ThreadLocals at Netflix (but hidden inside company-wide I'm not convinced this is something that is easily turned into a standard. I certainly would not support an intrusive and broad standard such as what Google enforces internally as per this (https://blog.golang.org/context - thanks @louiscryan for the links):
That definitely works, but is better left as a choice for each library, team, company, not as a broad standard. Is there anything further for us to do in RxJava on this topic? Does someone want to tackle providing better examples or documentation? Or does someone have a proposal for what an industry-wide Java standard would look like? Are there any design choices in RxJava 2.0 that we could do differently that would better support this functionality? |
@benjchristensen There is a discussion here: DistributedTracing/continuation-local-storage-jvm#1 RxJava is also mentioned there. We would have to build a non-intrusive construct so it would be transparent to the RxJava API and other userland APIs. There's some discussion about whether one should transfer state explicitly or implicitly. Feel free to have a look, and comment! |
Would the Akka concerns re: location-transparency be solveable by requiring the context be serializable? |
@robstarling In general, Java Serialization is more of a liability rather than a desirable solution. |
Touché, @viktorklang . Agreed that little has ever been solved by it. :) N/m. |
@robstarling Another option is to specify a binary coding, but I am not sure as to the value/benefit ratio. Keep in mind that the format would need to be possibly versioned etc. |
@viktorklang even with no versioning, a simple string would be really nice for associating a log or a failure with a triggering action. (or a uuid, or maybe even a long number -- it wouldn't have to be "strongly unique", just unique enough in a reasonable time scale for most debugging purposes). It really depends if we're just trying to target the Zipkin-like case, or something much more general. If the general solutions are all undesirable, it doesn't mean we should't consider focusing on the simpler case. Maybe have an initially-empty stack of uuids that propagates through future-creations, which code can read ("what's my context-chain?") and prepend to ("let this work and the work of futures i spawn have context X::...") |
@robstarling Perhaps I misunderstood, you'd have to store arbitrary Java Objects in this "continuation-local"? |
A simple primitive could be to only let it store either (1) long/64-bit/128-bit values or (2) object references; I guess they're both more or less interchangeable, in the sense that an object reference could be a pointer to a Long, or a Long value could be a key in a store (e.g. HashMap) which contains the actual value. |
@eirslett Agreed, and in my mind it is not only about serialization—it is about sending it to another system, and having that system able and willing to deserialize it. What possible serialization strategies exist, are they interoperable, are they versioned, how does framing work, etc. |
I'm closing this issue due to inactivity. If you have further input on the issue, don't hesitate to reopen this issue or post a new one. |
https://groups.google.com/forum/#!topic/rxjava/3iW-U6VZ_8c
I'm cross-posting this here, since I'm not quite sure where it belongs. One could think of it as a feature request. (Just close the issue if it shouldn't be on GitHub) The topic didn't get that much discussion on the mailing list...
The text was updated successfully, but these errors were encountered: