On 08/16/2013 12:19 PM, Laurent GR�GOIRE wrote:
> As for having one thread per updater, we could also use the fact that
> the standard Java "executor" framework allow for a pool of threads. So
> it may be a good trade-off between one single thread and one thread per
> client: a blocking client thus would not lock all updaters. Using the
> Java executor framework also make sure some nasty details of running a
> background tasks are properly handled by the library itself. The
> question would be to configure the number of threads in the pool.
Yes, the Executor API is nice and should be simpler for us than
low-level thread creation and management. We could have one
SingleThreadExecutor for the graph-writer tasks and one CachedThreadPool
for the fetch/decode tasks. Presumably both would be held by a
GraphUpdaterManager (ex-PeriodicTimerGraphUpdater) instance on a
per-Graph basis. The CachedThreadPool will grow without bound to
accommodate long-running updaters. The SingleThreadScheduledExecutor is
assuming we are putting Runnables on the queue, not update objects (see
below).
> I may not understand fully what the proposed plan is, but wouldn't it be
> simpler for event-driven threads to push updates to an update-object
> queue directly instead of pushing a Runnable making the update?
We discussed this possibility, but it requires us to have a class
representing each possible kind of update. We currently have them for
trip updates (to provide an internal lingua franca for multiple RT feed
formats), but do we want create event objects for every kind of graph
update (add bicycle rental station, etc.)? In the short term it will be
expedient to submit instances of a class implementing Runnable to the
queue, and constrain how those Runnables behave by convention rather
than force.
If we use a work queue rather than a SingleThreadExecutor, the lone
graph writer thread would just block waiting to consume update items off
the work queue. It might even be managed by the same CachedThreadPool as
the fetch/decode tasks.
> Also I would not favor using the ubiquitous Runnable interface but, as
> already discussed, creating a specific interface. It could be better for
> both readability and reverse engineering (I'm thinking of call-hierarchy
> source navigation).
I think we all favor having separate interfaces or base classes
(GraphUpdateReceiver/GraphUpdateApplier) to make things more clear and
readable. The issue here is a relatively minor one of style: inheritance
versus composition. Should GraphUpdateReceiver/Appliers extend/implement
Runnable, or instead be wrapped in a Runnable for submission to the
Executor? In my opinion the latter adds a bit of unnecessary code and
complexity. In either case, the GraphUpdateManager would only allow you
to submit(GraphUpdateReceiver/Applier), not submmit(Runnable).
> +1 for all of this. I was also thinking to make the updater a Graph
> field as it will probably become an important and mandatory service. In
> term of readability it's way simpler too.
In the long term I would actually lean toward removing the Services Map
from Graph, and explicitly listing out all extensions in separate
fields. There are only a few Graph instances per OTP server, so null
fields are harmless.
>> 1. Could we get rid of the PreferencesConfigurable interface and just
>> merge it into GraphUpdater? Is there anything we need to configure with
>> preferences that is not in some sense a graph updater thread?
>
> As for merging the two classes, at first sight I would have preferred to
> keep an interface, but as I do not see any way in Java to ensure
> constructor signature using interface this is probably the best way to go.
We had exactly this conversation at GoAbout :) I can see the argument
for the inability to require constructor signatures, since a particular
concrete subclass may need more information to construct than the base
class / interface it is extending/implementing. Anyway, required factory
methods are workable (and maybe even more readable), as demonstrated in
my sample code. We also agreed that well-documented convention was more
important than devising interfaces that force us to behave in a
particular way.
> As for other configuration types, for now I don't see anything. Maybe as
> discussed with "povder" on the dynamic car speed proposal some new
> updater could come, but they would be "updaters" too.
We also couldn't think of any non-graph-updater uses. Let's just keep it
simple for now and avoid abstracting out configurability until we
actually have a use case.
>> 2. Do the configurators need to be separate from the configured? Can we
>> for example merge BikeRentalConfigurator into BikeRentalUpdater2 since
>> BikeRentalConfigurator always makes an instance of this class?
>
> They do not need to be. I kept them separated to prevent the updater
> stuff from depending on the configuration stuff, and to make it easier
> to keep Spring/DI backward-compatibility. Having one or two classes is
> more a matter of taste.
Great, then I would favor merging them for clarity. Personally I don't
really mind if new features (e.g. graph updater configuration) are not
Spring-aware. Anyway, once the standalone mode has seen some more
testing, we could just make the legacy servlet a wrapper around the
Spring-less code used by the standalone server.
> As said before, for the record, I would just remove the Runnable
> interface and add a run() method (with it's proper throws clause if
> needed) to the GraphUpdater interface itself.
Sorry to make a conversation out this minor issue, but I think I'm
missing something. What is the downside of extending Runnable, as long
as we make the details of the GraphUpdateManager private and make the
task or updater submission methods only accept our Runnable subinterfaces?
> I agree it's way simpler with a switch, but this prevent from
> dynamically injecting new configurator from external libraries, as I was
> proposing in an earlier thread for dynamic car speed changes. But I'm
> not sure this is an important feature and we can probably live without this.
At this stage, I think such dynamic injection would create more problems
than it solves. One of the big style difficulties we have encountered in
OTP is premature generalization and "everything must be a pluggable
framework". Developers are free to fork OTP and add graph updaters for
their own use. If our updater architecture is sound and flexible (which
I expect it is after this conversation), it should be painless to keep
that special-purpose updater code self-contained in a package and
contribute it as a pull request.
> We could also use a factory interface (with the factory object providing
> both the key and creating the updater), or using reflection and calling,
> on a provided class, either 1) a static method "fromPreferences" or 2) a
> constructor with the preferences as parameter. It's up to you!
If we're going to get very pluggable, Updaters could even provide their
own key Strings via an interface method. But as amusing as infinitely
extensible external plugin systems are, I don't really see a strong use
case for them in OTP.
> It's way simpler to embed the preferences as keys on the graph itself, I
> agree. I made it part of a keyed-service to again prevent too much
> dependencies from the core on the configurator stuff, but it's probably
> not worth the trouble. Having this field would be simpler indeed.
I think configuration of graph updaters is here to stay, and don't mind
dependencies here. My point of view is: don't hesitate to put optional
service fields directly on the Graph!
> If we serialize the updater object/thread themselves with the graph, we
> could run into initialization issues (I'm thinking of starting up
> threads, network connections, etc...) and this could soon become a
> maintenance burden, especially that this mode would not be used that
> often.
You're right, serializing the updaters could get ugly. It was wrapping
the preferences in an ad-hoc "service" that made me question storing
them in the Graph. If they get their own field with a clear purpose the
current approach should work fine. I do wonder whether we need to embed
preferences at all though.
-Andrew