Skip to content

ThreadPoolExecutor#shutdown? inconsistency in JRuby and C Ruby #1041

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

Closed
bensheldon opened this issue Feb 29, 2024 · 4 comments · Fixed by #1042
Closed

ThreadPoolExecutor#shutdown? inconsistency in JRuby and C Ruby #1041

bensheldon opened this issue Feb 29, 2024 · 4 comments · Fixed by #1042

Comments

@bensheldon
Copy link
Contributor

In the scenario in which a ThreadPoolExecutor has received #shutdown and there are active threads/tasks, there is an inconsistency between what JRuby and C Ruby return for #shutdown?:

  • JRuby will return #shutdown? => true while there are still active threads/tasks, at the same time as #shuttingdown? => true. Java's executor service narrowly defines #isShutdown as "no new tasks will be accepted" which is distinct from #isTerminated which is "all tasks have completed following shut down".
  • The Ruby implementation does more of what my expectations are: #shutdown? => false while there are still active threads/tasks, at the same time as #shuttingdown? => true.

It's fairly easy to work around this inconsistency using executor.shutdown? && !executor.shuttingdown? to achieve the same result in both rubies for determining when there are no more threads/tasks on a shutdown executor. It would be nice if the behavior was aligned.

@bensheldon
Copy link
Contributor Author

Another discovery here is that in J Ruby, once ThreadPoolExecutor#kill is called, it's necessary to do a wait_for_termination to achieve a fully shutdown and terminated executor.

This is now the shutdown wrapper I'm using:

# @param timeout [Numeric, nil] Seconds to wait for active threads.
#   * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
#   * +-1+, the scheduler will wait until the shutdown is complete.
#   * +0+, the scheduler will immediately shutdown and stop any threads.
#   * A positive number will wait that many seconds before stopping any remaining active threads.
def shutdown(timeout: -1)
  return if @executor.nil? || (@executor.shutdown? && !@executor.shuttingdown?)

  @executor.shutdown if @executor.running?

  if @executor.shuttingdown? && timeout # rubocop:disable Style/GuardClause
    executor_wait = timeout.negative? ? nil : timeout
    unless @executor.wait_for_termination(executor_wait)
      @executor.kill
      @executor.wait_for_termination
    end
  end
end

This is similar to the usage example given in Java:

 void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

@eregon
Copy link
Collaborator

eregon commented Feb 29, 2024

@bensheldon
Copy link
Contributor Author

Thanks! This line does look like the culprit:

def ns_shutdown?
@executor.isShutdown || @executor.isTerminated
end

I missed that last night because I got distracted trying to discover the need for the feature-check in #shuttingdown?:

def ns_shuttingdown?
if @executor.respond_to? :isTerminating
@executor.isTerminating
else
false
end

That was introduced 9+ years ago (I couldn't find the exact place), but I'm not sure if isTerminating/isTerminated only exist for certain implementations or versions of Java. Any idea how to check that?

@bensheldon
Copy link
Contributor Author

Aha, it looks like just isTerminating maybe didn't exist in Java 6, but isTerminated did. ok, I think I have enough for a PR 👍

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

Successfully merging a pull request may close this issue.

2 participants