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

1.0 Roadmap #142

Closed
jdantonio opened this issue Aug 6, 2014 · 19 comments
Closed

1.0 Roadmap #142

jdantonio opened this issue Aug 6, 2014 · 19 comments
Labels
chore Gem maintenance tasks.
Milestone

Comments

@jdantonio
Copy link
Member

I'd like us to consider making our next release after 0.7 our 1.0 release. We've made significant gains over the spring and summer. We've deprecated a ton of my earlier experimental stuff, fixed many bugs, greatly improved the reliability of our test suite, merged ruby-atomic, created an automated build process for compiling platform-specific gems, and added an entirely new and feature-rich actor framework. That's an amazing amount of work. It seems to me that we've now implemented most of the "must have" items and are now looking mostly at optimizations, refactoring, and "nice to have" features. If everyone else agrees, then I think we can start planning a 1.0 release.

We've had previous discussions here and here but a lot has changed since then. Starting the discussion anew seems appropriate.

Thoughts?

We can use this issue to discuss specific features and updates for the eventual 1.0 release.

@jdantonio
Copy link
Member Author

Things we may want to include in a 1.0 release:

  • Automated performance tests: @chrisseaton has done some amazing performance testing (examples here and here). He has suggested that we create a suite of automated performance tests for this gem.
  • New Promise: @dsisnero correctly noted that our Promise implementation has evolved to be very similar to our Future and has suggested we look to Scala for inspiration (here and here). I am comfortable with deprecating the existing promise implementation in the final 0.7.0 release and creating a new promise implementation in 1.0.

@chuckremes
Copy link

If you plan to implement a new Promise API, you should allow some time for it to stabilize. Once you go 1.0, the API should really be frozen (assuming you are following semantic versioning).

So, I’d recommend a 0.8 (or similar) to allow time for any new APIs to shake out before declaring 1.0.

On Aug 6, 2014, at 9:13 AM, jdantonio [email protected] wrote:

Things we may want to include in a 1.0 release:

Automated performance tests: @chrisseaton has done some amazing performance testing (examples here and here). He has suggested that we create a suite of automated performance tests for this gem.
New Promise: @dsisnero correctly noted that our Promise implementation has evolved to be very similar to our Future and has suggested we look to Scala for inspiration (here and here). I am comfortable with deprecating the existing promise implementation in the final 0.7.0 release and creating a new promise implementation in 1.0.

Reply to this email directly or view it on GitHub.

@chrisseaton
Copy link
Member

I will recommit to having the fork-join stuff in over the next couple of weeks.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 6, 2014

other things to consider:

  1. Clojure aligned agent implementation integrated with STM to by able to use clojures impl. on JRuby later for performance.
  2. OOPish agents where sending symbol will lead in execution of agent's method with same name, something like predefined functions.
  3. Remote actors and support for Akka's un/become.
  4. Integration of concurrent-ruby parts. Possibly through channels: anything can send message to them already, but we need an effective way how to connect agents, actors, thread-pools to channels output.
  5. IO integration. At least few examples how to read effective IO (files, sockets, connections) and distribute them to other abstractions. (I have one example in mind for actors, I'll should push it soon.)
  6. Renaming global thread pools to indicate better that blocking and long tasks should never go to global task pool.
  7. Fix termination of global pools held in configuration. E.g. it's terminated before test suite runs if the pools are used in test suite of other gem/app.
  8. Create a World object and move any global stuff there. User would have explicitly load require 'concurrent/single_world' to get single global space for their convenience, otherwise worlds would need to be created manually. TL;DR i think we should avoid any global states in this kind of gem.

I think we are getting close too, but I would do 0.8 first. If that works out we can promote to 1.0 with just few bugfixes and freeze the API.

@chrisseaton
Copy link
Member

We should be careful that the world object doesn't become something that tries to own too much or is too self-important. I like the fact at the moment that you can drop our features in and out of a program. I think we want to avoid a case where you have to create or init a global world object at the start of your program. I remember libraries like SDL or wxWidgets that always wanted to 'own' your program like that.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 6, 2014

@chrisseaton i see your point, we need to keep it simple to use. That's why I've also suggested to create a global world for convenience if user chooses to require it.

On the other hand forcing the global world always can be problematic in tests and when combining e.g. two rails engines where both were already using concurrent-ruby in different ways. Having an global objects bit me too many times already, so I try to use suggested pattern to keep flexibility.

# file:world.rb
class World
  attr_reader :configuration # or possibly merge configuration into World
  # etc.
end

# file:single_world.rb, needs to be required
WORLD = World.new

@chrisseaton
Copy link
Member

I see the use case - multiple parts of an application all using concurrency in different ways. That does seem like good software engineering in general, but with concurrency things are different - you don't want two thread pools that each think they should create as many threads as there are cores. I think concurrency is just a global concern.

Even the name World strikes me as extremely framework-y. If it's there for configuration, it should be called something like that. I don't think it should also include pools, for the reason above. What else would be in it?

I think this suits your actor stuff well where the framework needs to own the program to an extent, but works less well for things like futures and dataflow where it's very much the program just adding a few extra method calls here and there.

@pitr-ch pitr-ch added the chores label Aug 6, 2014
@chrisseaton
Copy link
Member

@jdantonio Regarding Scala promises - I think they're basically an IVar with more methods for chaining things onto their completion. I think this means it makes sense to make a Promise a subclass of IVar. In Scala I believe a Future is also a Promise. So we'd have IVar -> Promise -> Future.

All these terms a bit muddled between different implementations. Future is a particularly abused term - when it was first introduced the whole idea was that there wasn't a Future type - you just had the type it delivered and were unaware it was asynchronous.

Future[T] is a contradiction in terms!

@pitr-ch
Copy link
Member

pitr-ch commented Aug 7, 2014

@chrisseaton the Future is usually also a Promis, but there could also be Future and Promise as two different companion objects. I'll submit prototype to help with the discussion.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 7, 2014

world: @chrisseaton hm I did not think about the two big pools issue. An user would need to reduce the sizes to be able to keep both because of different overflow policies for example or inject one pool to the second world.

What about: keeping the global thread pools but remove any configuration so they are always same in any application. If user needs custom configured thread-pool he can create other pool and use it as needed inserting it into his objects. This may be an unpopular step, but I think it makes sense to keep the global thread pools only for its specified usage and disallow reconfiguration for other purpose.

@jdantonio
Copy link
Member Author

The reason I created a global thread pool was to keep the simple use cases as simple as possible. This blog post is the perfect example. A user creates a bunch of futures for a simple task. If we don't have a global thread pool then the program changes entirely. One option would be for each future to get its own thread, which would be very inefficient. Another option would be to require the every user to manually configure a thread pool any time they want to use the gem. This would be a huge barrier to adoption.

There are a couple of things that I like about the current setup:

  • The simple use case remains simple
  • Advanced users can reconfigure the global thread pool to suit their needs
  • Advanced users can inject different executors into different objects for fine-grained control
  • Because the global pools are encapsulated in Delay objects the global pools don't run unless used

One of my influences for the global thread pool was Clojure's agent which creates a single global pool for all agents. Unlike our current global thread pool, a user of Clojure's agents cannot reconfigure the pool.

I'm definitely willing to revisit the idea of a global thread pool and change the way we do things. But I think we do a disservice if we force our users to always manually configure the gem. I this we should try to provide a simple yet reasonably performant default that serves the need of users like the one in the previously linked blog post.

One other thing we need to consider is they type of application most likely to be written by our users. This is a Ruby library, not a Java or Scala library. I don't personally know any Ruby programmers who are writing processor-intensive applications. All of the Ruby programmers I know write IO-intensive applications. Limiting the global thread pool to one-thread-per-processor probably wouldn't be a very efficient approach. And it wouldn't help MRI users at all since MRI only uses one core.

I know that it's impossible to know in advance how our users are going to use this gem, but I think we can make some reasonable guesses given the domains Ruby is most commonly used in.

@lucasallan
Copy link
Member

I think we might need a couple more releases before 1.0. As @chuckremes mentioned, we need to make sure that we have a well defined and final api before release 1.0.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 7, 2014

@jdantonio yeah i think in general we agree. I want to keep simplicity and flexibility too. I worry about the case of integrating two applications where each configures the global threads differently.

To improve that use case I think there are 2 ways.
First:

  • The simple use case remains simple
  • Advanced users can reconfigure the global thread pool to suit their needs
  • Advanced users can inject different executors into different objects for fine-grained control
  • Because the global pools are encapsulated in Delay objects the global pools don't run unless used

I think the 3. option is enough for the flexibility. If we agree on removing the configuration of global thread pools we could remove configuration and move the global pools directly under Concurrent module.

Second:
As previously suggested: Creation of world object holding pools and configuration (logging). A single global world would be provided for convenience.

I think we can go either way. Did I omit other options?

Btw what are your thoughts one the other points I wrote in #142 (comment)?

@elcuervo elcuervo mentioned this issue Aug 7, 2014
@mighe
Copy link
Contributor

mighe commented Aug 8, 2014

Sorry for the delay!

IVar - Future - Promise relation

Our gem is probably one of the few library that provides the IVar abstraction. At the moment Future is just an executable IVar, adding very few behaviour to it, while a Promise is something more sophisticated because it allows task chaining.

If we want to follow the Scala's way the result will be:

  • 'IVar' will be untouched (at most we'll add the method we have in Probe and remove the latter)
  • Future will gain chaining methods and lose #execute
  • Promise will no longer inherit from Obligation and gain methods to execute its companion Future

Right? (I'm trying to recap)

Configuration

@pitr-ch can you explain me the difference between World and the configuration class? Since we want to keep the current simplicity of sensible defaults and the flexibility we already have, it seems to me they are the same thing with just a different name.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 8, 2014

@mighe
Ivar: Yep sounds good to me. I hope I'll have a prototype soon.
configuration: The difference are not big. The main difference is that even if configuration is a class and can have many instances we can use only one globally set and accessible on Concurrent.configuration. The proposed world variant wold allow to create agents, actors in any World instance. (The name world is maybe not the best it would hold mostly configuration anyway.)

I am starting to incline to the first option: to remove configuration from the global pools. It would be same as in clojure agents: there are send and send-off methods using the global pools and send-via which allows to use a different pool. I think that's a good model.

@jdantonio
Copy link
Member Author

@pitr-ch Sorry for the late reply. I've been very busy the past couple of weeks. I plan to spend the day today catching up. I'll post some additional thoughts later today.

@jdantonio
Copy link
Member Author

Random thoughts, in no particular order:

  • There seems to be strong support for a 0.8.0 release, so we'll do that.
  • @chrisseaton has offered to work on a fork-join implementation, which would be a great addition. We'll make that part of the 1.0 roadmap, but include it in the first release for which it is ready.
  • A community member (@alexdowad) has volunteered to merge his read-write-lock. It looks like it will be a great addition. We'll make that part of the 1.0 roadmap, but include it in the first release for which it is ready.
  • @pitr-ch has suggested remote actors. This is something @lucasallan and I have been talking about for quite a while (we would live a pluggable transport layer, for example). We should collaborate on a plan for the 1.0 roadmap.
  • Akka-like un/become for actors would be awesome, so I'll leave that in the hands of @pitr-ch.
  • I originally based Agent Clojure's agent, but that was one of the earliest things I built, and I've learned a lot since then. As with Promise I am comfortable with completely refactoring that class. @pitr-ch, can you provide more detail? This sounds like something I would be interesting in working on, too.
  • To be perfectly honest, I haven't worked with Channel at all (sorry @mighe!) so I don't completely follow the suggestion from @pitr-ch regarding the integration of the various abstractions via channels. But I'm curious to hear more.
  • Better examples of IO integration would be great.
  • I have no problem with renaming the global thread pools. My expectation is that most users wouldn't interact with them directly.
  • For the same reason, I am comfortable with removing the ability of the user to reconfigure the global thread pools. Thanks to @mighe's early influence, we've updated most of the high-level abstractions to allow the direct injection of a custom executor. In most cases I would expect that to be a better model.
  • It sounds like @mighe, @chrisseaton, and @pitr-ch have similar ideas about IVar, Future, and Promise. I'm happy to let the three of you work out the details. With respect to a comment from @mighe, does the addition of chaining to Future require the removal of the #execute method? The #execute method could be used to create a future that cannot be chained. I don't feel strongly one way or the other.

@pitr-ch
Copy link
Member

pitr-ch commented Aug 10, 2014

agents: I was referring to e.g. #70 but we should probably go through agent clojure's documentation in detail and check the behavior. Another point was integration with STM transaction as in clojure (functions are dispatched to agents after commit and only once). Then we should be compatible with clujure enough to be able to use clujure's implementation on JRuby (this is possibly after 1.0).

The OOPing of the agents needs more discussion, it may be a nonsense. E.g.

class MyAgent < Agent
  def do_stuff(old_value)
    old_value + 1
  end
end

agent = MyAgent.new
agent.post :do_stuff # but what if :do_stuf is blocking and should go to #post_off?

channel integration: channels are like blocking queues but user can #select on them like on IO. When there is no message it will block the thread which is not usable in conjunction with actors. My thought was to create an effective integration that would send any message from a channel to an actor (without blocking or needing extra thread to listen the channel). Just a raw idea maybe there is a better way how to integrate abstractions we have together.

@jdantonio jdantonio mentioned this issue Aug 12, 2014
49 tasks
@pitr-ch pitr-ch modified the milestone: 1.0.0 Release Oct 30, 2014
@jdantonio
Copy link
Member Author

Moved to #257

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chore Gem maintenance tasks.
Projects
None yet
Development

No branches or pull requests

6 participants