Tuesday, May 14, 2013

Concurrent Tasks In JRuby

Sometimes you need to execute long running tasks (like database queries on multiple shards) concurrently. JRuby can easily make use of Java's built-in Executor class for this purpose.

# Since java.util.concurrent.ThreadPoolExecutor.submit has multiple signatures,
# we force JRuby to use the one signature we want, and stop a warning from being emitted.
java.util.concurrent.ThreadPoolExecutor.class_eval do
  java_alias :submit, :submit, [java.util.concurrent.Callable.java_class]
end

# Start the executor service.  Note that this won't create any threads right away,
# but will create new threads as the jobs are submitted, until it gets to 5 threads.
# Then it will reuse the threads.
executor = java.util.concurrent.Executors.new_fixed_thread_pool 5

# Submit jobs, get back java.util.concurrent.FutureTask objects.
tasks = [1, 3, 4, 6].map do |s|
  executor.submit do
    # run database query on shard s
    # return query result
  end
end

# We get control back immediately, while the queries are being executed by the thread pool.
# We can do some other things here while the queries run.

# Get the results of all queries and combine them into an array.  FutureTask.get will wait
# for the task to complete if it's not completed yet, so it's safe to call this right away.
# If for some reason we wanted to check if a task is complete, we can call t.done? .
results = tasks.map do |t|
  begin
    t.get
  rescue java.util.concurrent.ExecutionException => e
    # Convert the exception to one actually thrown inside the task.
    raise e.cause.exception
  end
end

# Shut down the thread pool.
executor.shutdown_now

No comments: