You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by Poison <ti...@qq.com.INVALID> on 2021/10/24 03:44:24 UTC

About the comment of org.apache.tomcat.util.threads.TaskQueue

Tomcat version: 8.5.72


org.apache.tomcat.util.threads.TaskQueue&nbsp;source code:&nbsp;https://github.com/apache/tomcat/blob/8.5.72/java/org/apache/tomcat/util/threads/TaskQueue.java#L33


In the comments of the&nbsp;TaskQueue&nbsp;class, it mentions "If you use a normal queue, the executor will spawn threads when there are idle threads and you wont be able to force items onto the queue itself.".&nbsp;But when we use java.util.concurrent.LinkedBlockingQueue, it will be put into the queue by default instead of creating threads. What queue does the normal queue in the comment refer to?


Thanks,
tianshuang

Re: [OT] About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

On 10/26/21 15:56, Mark Thomas wrote:
> On 26/10/2021 09:47, Poison wrote:
>> Thank you for your detailed explanation. Now I understand the 
>> background of this part of the comment. When corePoolSize is equal to 
>> maxThreads, the native implementation will create threads first.
>>
>>
>> There is another question. Why does 
>> org.apache.tomcat.util.threads.ThreadPoolExecutor almost copy the code 
>> of java.util.concurrent.ThreadPoolExecutor instead of implementing it 
>> by inheriting java.util.concurrent.ThreadPoolExecutor? This is what I 
>> don't understand.&nbsp;Hope you can explain the design concept behind, 
>> thank you.
> 
> j.u.c.ThreadPoolExecutor isn't designed in a way we can extend it and 
> change the things we need to change. Copying it was the only option.

It's interesting that the original license of the sources created for 
JSR-166 were released to the public domain aka claiming "no copyright". 
Subsequently, Oracle added the GPL license (to match the Java source 
code policy) and, similarly yet separately, the ASF added an AL2 to 
another copy of the code.

So virtually the same code is available under 2 licenses as well as "do 
whatever you want" terms, separately.

Odd.

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Poison <ti...@qq.com.INVALID>.
Thank you. After I checked the Git submission record of org.apache.tomcat.util.threads.ThreadPoolExecutor, I found that the code of java.util.concurrent.ThreadPoolExecutor was copied to solve bug 65454. Early org.apache.tomcat.util. threads.ThreadPoolExecutor is implemented by inheriting java.util.concurrent.ThreadPoolExecutor. Because the code to fix the bug 65454 needs to modify the code in the java.util.concurrent.ThreadPoolExecutor.Worker class, it cannot be implemented by inheriting java.util.concurrent.ThreadPoolExecutor at this time.&nbsp;Now I understand why this is needed, thank you for your explanation.


https://bz.apache.org/bugzilla/show_bug.cgi?id=65454#c7


------------------&nbsp;Original&nbsp;------------------
From:                                                                                                                        "Tomcat Users List"                                                                                    <markt@apache.org&gt;;
Date:&nbsp;Wed, Oct 27, 2021 05:11 PM
To:&nbsp;"users"<users@tomcat.apache.org&gt;;

Subject:&nbsp;Re: About the comment of org.apache.tomcat.util.threads.TaskQueue



On 27/10/2021 02:58, Poison wrote:
&gt; Ok, I'm just curious, because org.apache.catalina.tribes.util.ExecutorFactory.TribesThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor but org.apache.tomcat.util.threads.ThreadPoolExecutor does not.

They are implementing different behaviours.

&gt; Similarly, in the open source project dubbo, EagerThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor to achieve similar functions as the tomcat thread pool.
&gt; 
&gt; 
&gt; https://github.com/apache/dubbo/blob/dubbo-2.7.5/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPoolExecutor.java#L30

The Dubbo implementation does not appear to achieve the behaviour Tomcat 
requires.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org

Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Mark Thomas <ma...@apache.org>.
On 27/10/2021 02:58, Poison wrote:
> Ok, I'm just curious, because org.apache.catalina.tribes.util.ExecutorFactory.TribesThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor but org.apache.tomcat.util.threads.ThreadPoolExecutor does not.

They are implementing different behaviours.

> Similarly, in the open source project dubbo, EagerThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor to achieve similar functions as the tomcat thread pool.
> 
> 
> https://github.com/apache/dubbo/blob/dubbo-2.7.5/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPoolExecutor.java#L30

The Dubbo implementation does not appear to achieve the behaviour Tomcat 
requires.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Poison <ti...@qq.com.INVALID>.
Ok, I'm just curious, because org.apache.catalina.tribes.util.ExecutorFactory.TribesThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor but org.apache.tomcat.util.threads.ThreadPoolExecutor does not.


Similarly, in the open source project dubbo, EagerThreadPoolExecutor inherits java.util.concurrent.ThreadPoolExecutor to achieve similar functions as the tomcat thread pool.


https://github.com/apache/dubbo/blob/dubbo-2.7.5/dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/support/eager/EagerThreadPoolExecutor.java#L30




------------------&nbsp;Original&nbsp;------------------
From:                                                                                                                        "Tomcat Users List"                                                                                    <markt@apache.org&gt;;
Date:&nbsp;Wed, Oct 27, 2021 03:56 AM
To:&nbsp;"users"<users@tomcat.apache.org&gt;;

Subject:&nbsp;Re: About the comment of org.apache.tomcat.util.threads.TaskQueue



On 26/10/2021 09:47, Poison wrote:
&gt; Thank you for your detailed explanation. Now I understand the background of this part of the comment. When corePoolSize is equal to maxThreads, the native implementation will create threads first.
&gt; 
&gt; 
&gt; There is another question. Why does org.apache.tomcat.util.threads.ThreadPoolExecutor almost copy the code of java.util.concurrent.ThreadPoolExecutor instead of implementing it by inheriting java.util.concurrent.ThreadPoolExecutor? This is what I don't understand.&amp;nbsp;Hope you can explain the design concept behind, thank you.

j.u.c.ThreadPoolExecutor isn't designed in a way we can extend it and 
change the things we need to change. Copying it was the only option.

Mark


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org

Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Mark Thomas <ma...@apache.org>.
On 26/10/2021 09:47, Poison wrote:
> Thank you for your detailed explanation. Now I understand the background of this part of the comment. When corePoolSize is equal to maxThreads, the native implementation will create threads first.
> 
> 
> There is another question. Why does org.apache.tomcat.util.threads.ThreadPoolExecutor almost copy the code of java.util.concurrent.ThreadPoolExecutor instead of implementing it by inheriting java.util.concurrent.ThreadPoolExecutor? This is what I don't understand.&nbsp;Hope you can explain the design concept behind, thank you.

j.u.c.ThreadPoolExecutor isn't designed in a way we can extend it and 
change the things we need to change. Copying it was the only option.

Mark


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Poison <ti...@qq.com.INVALID>.
Thank you for your detailed explanation. Now I understand the background of this part of the comment. When corePoolSize is equal to maxThreads, the native implementation will create threads first.


There is another question. Why does org.apache.tomcat.util.threads.ThreadPoolExecutor almost copy the code of java.util.concurrent.ThreadPoolExecutor instead of implementing it by inheriting java.util.concurrent.ThreadPoolExecutor? This is what I don't understand.&nbsp;Hope you can explain the design concept behind, thank you.




------------------&nbsp;Original&nbsp;------------------
From:                                                                                                                        "Tomcat Users List"                                                                                    <markt@apache.org&gt;;
Date:&nbsp;Tue, Oct 26, 2021 03:08 PM
To:&nbsp;"users"<users@tomcat.apache.org&gt;;

Subject:&nbsp;Re: About the comment of org.apache.tomcat.util.threads.TaskQueue



On 26/10/2021 02:45, Poison wrote:
&gt; Thank you, I know the role of TaskQueue, but the comment about "normal queue" on the TaskQueue class is still incomprehensible.
&gt; 
&gt; 
&gt; In the java.util.concurrent.ThreadPoolExecutor#execute method, the comment mentions: "3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task.". Explain that the JDK prioritizes putting tasks into the queue rather than creating threads first, which is not consistent with the statement that "normal queue" mentioned in TaskQueue prioritizes thread creation.

You need to look at the custom queue and custom ThreadPoolExecutor together.

&nbsp;From the Javadoc of j.u.c.ThreadPoolExecutor:

<quote&gt;
&nbsp; * When a new task is submitted in method {@link #execute(Runnable)},
&nbsp; * if fewer than corePoolSize threads are running, a new thread is
&nbsp; * created to handle the request, even if other worker threads are
&nbsp; * idle.&nbsp; Else if fewer than maximumPoolSize threads are running, a
&nbsp; * new thread will be created to handle the request only if the queue
&nbsp; * is full.&nbsp; By setting corePoolSize and maximumPoolSize the same, you
&nbsp; * create a fixed-size thread pool. By setting maximumPoolSize to an
&nbsp; * essentially unbounded value such as {@code Integer.MAX_VALUE}, you
&nbsp; * allow the pool to accommodate an arbitrary number of concurrent
&nbsp; * tasks. Most typically, core and maximum pool sizes are set only
&nbsp; * upon construction, but they may also be changed dynamically using
&nbsp; * {@link #setCorePoolSize} and {@link #setMaximumPoolSize}.
</quote&gt;

The problem is that the default behaviour prioritises in this way:
- start corePoolSize threads
- queue tasks
- start additional threads up to maximumPoolSize if the queue is full

That isn't the behaviour we want in Tomcat. In Tomcat we want:
- start corePoolSize threads
- start additional threads up to maximumPoolSize
- queue tasks using an (effectively) infinite queue

That change has significant implications. These are most obviously seen 
in Tomcat's TaskQueue.offer(Runnable) implementation.

The closest you can get to the required Tomcat behaviour with 
j.u.c.ThreadPoolExecutor is by setting corePoolSize to maxThreads. 
However, this has the side effect that threads will be created for each 
new task until maxThreads have been started - hence the comment that it 
prioritises thread creation rather than re-using idle threads. You 
essentially end up with a fixed size thread pool of maxThreads - rather 
than what we want which (by default) is a thread pool of at least 
minSpareThreads that grows, if necessary, up to maxThreads and then 
shrinks back down to minSpareThreads while idle.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org

Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Mark Thomas <ma...@apache.org>.
On 26/10/2021 02:45, Poison wrote:
> Thank you, I know the role of TaskQueue, but the comment about "normal queue" on the TaskQueue class is still incomprehensible.
> 
> 
> In the java.util.concurrent.ThreadPoolExecutor#execute method, the comment mentions: "3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task.". Explain that the JDK prioritizes putting tasks into the queue rather than creating threads first, which is not consistent with the statement that "normal queue" mentioned in TaskQueue prioritizes thread creation.

You need to look at the custom queue and custom ThreadPoolExecutor together.

 From the Javadoc of j.u.c.ThreadPoolExecutor:

<quote>
  * When a new task is submitted in method {@link #execute(Runnable)},
  * if fewer than corePoolSize threads are running, a new thread is
  * created to handle the request, even if other worker threads are
  * idle.  Else if fewer than maximumPoolSize threads are running, a
  * new thread will be created to handle the request only if the queue
  * is full.  By setting corePoolSize and maximumPoolSize the same, you
  * create a fixed-size thread pool. By setting maximumPoolSize to an
  * essentially unbounded value such as {@code Integer.MAX_VALUE}, you
  * allow the pool to accommodate an arbitrary number of concurrent
  * tasks. Most typically, core and maximum pool sizes are set only
  * upon construction, but they may also be changed dynamically using
  * {@link #setCorePoolSize} and {@link #setMaximumPoolSize}.
</quote>

The problem is that the default behaviour prioritises in this way:
- start corePoolSize threads
- queue tasks
- start additional threads up to maximumPoolSize if the queue is full

That isn't the behaviour we want in Tomcat. In Tomcat we want:
- start corePoolSize threads
- start additional threads up to maximumPoolSize
- queue tasks using an (effectively) infinite queue

That change has significant implications. These are most obviously seen 
in Tomcat's TaskQueue.offer(Runnable) implementation.

The closest you can get to the required Tomcat behaviour with 
j.u.c.ThreadPoolExecutor is by setting corePoolSize to maxThreads. 
However, this has the side effect that threads will be created for each 
new task until maxThreads have been started - hence the comment that it 
prioritises thread creation rather than re-using idle threads. You 
essentially end up with a fixed size thread pool of maxThreads - rather 
than what we want which (by default) is a thread pool of at least 
minSpareThreads that grows, if necessary, up to maxThreads and then 
shrinks back down to minSpareThreads while idle.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Poison <ti...@qq.com.INVALID>.
Thank you, I know the role of TaskQueue, but the comment about "normal queue" on the TaskQueue class is still incomprehensible.


In the java.util.concurrent.ThreadPoolExecutor#execute method, the comment mentions: "3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task.". Explain that the JDK prioritizes putting tasks into the queue rather than creating threads first, which is not consistent with the statement that "normal queue" mentioned in TaskQueue prioritizes thread creation.


java.util.concurrent.ThreadPoolExecutor#execute:



/**
&nbsp;* Executes the given task sometime in the future.&nbsp; The task
&nbsp;* may execute in a new thread or in an existing pooled thread.
&nbsp;*
&nbsp;* If the task cannot be submitted for execution, either because this
&nbsp;* executor has been shutdown or because its capacity has been reached,
&nbsp;* the task is handled by the current {@code RejectedExecutionHandler}.
&nbsp;*
&nbsp;* @param command the task to execute
&nbsp;* @throws RejectedExecutionException at discretion of
&nbsp;*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{@code RejectedExecutionHandler}, if the task
&nbsp;*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;cannot be accepted for execution
&nbsp;* @throws NullPointerException if {@code command} is null
&nbsp;*/
public void execute(Runnable command) {
&nbsp; &nbsp; if (command == null)
&nbsp; &nbsp; &nbsp; &nbsp; throw new NullPointerException();
&nbsp; &nbsp; /*
&nbsp; &nbsp; &nbsp;* Proceed in 3 steps:
&nbsp; &nbsp; &nbsp;*
&nbsp; &nbsp; &nbsp;* 1. If fewer than corePoolSize threads are running, try to
&nbsp; &nbsp; &nbsp;* start a new thread with the given command as its first
&nbsp; &nbsp; &nbsp;* task.&nbsp; The call to addWorker atomically checks runState and
&nbsp; &nbsp; &nbsp;* workerCount, and so prevents false alarms that would add
&nbsp; &nbsp; &nbsp;* threads when it shouldn't, by returning false.
&nbsp; &nbsp; &nbsp;*
&nbsp; &nbsp; &nbsp;* 2. If a task can be successfully queued, then we still need
&nbsp; &nbsp; &nbsp;* to double-check whether we should have added a thread
&nbsp; &nbsp; &nbsp;* (because existing ones died since last checking) or that
&nbsp; &nbsp; &nbsp;* the pool shut down since entry into this method. So we
&nbsp; &nbsp; &nbsp;* recheck state and if necessary roll back the enqueuing if
&nbsp; &nbsp; &nbsp;* stopped, or start a new thread if there are none.
&nbsp; &nbsp; &nbsp;*
&nbsp; &nbsp; &nbsp;* 3. If we cannot queue task, then we try to add a new
&nbsp; &nbsp; &nbsp;* thread.&nbsp; If it fails, we know we are shut down or saturated
&nbsp; &nbsp; &nbsp;* and so reject the task.
&nbsp; &nbsp; &nbsp;*/
&nbsp; &nbsp; int c = ctl.get();
&nbsp; &nbsp; if (workerCountOf(c) < corePoolSize) {
&nbsp; &nbsp; &nbsp; &nbsp; if (addWorker(command, true))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; &nbsp; &nbsp; c = ctl.get();
&nbsp; &nbsp; }
&nbsp; &nbsp; if (isRunning(c) &amp;&amp; workQueue.offer(command)) {
&nbsp; &nbsp; &nbsp; &nbsp; int recheck = ctl.get();
&nbsp; &nbsp; &nbsp; &nbsp; if (! isRunning(recheck) &amp;&amp; remove(command))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reject(command);
&nbsp; &nbsp; &nbsp; &nbsp; else if (workerCountOf(recheck) == 0)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addWorker(null, false);
&nbsp; &nbsp; }
&nbsp; &nbsp; else if (!addWorker(command, false))
&nbsp; &nbsp; &nbsp; &nbsp; reject(command);
}



------------------&nbsp;Original&nbsp;------------------
From:                                                                                                                        "Tomcat Users List"                                                                                    <chris@christopherschultz.net&gt;;
Date:&nbsp;Tue, Oct 26, 2021 02:33 AM
To:&nbsp;"users"<users@tomcat.apache.org&gt;;

Subject:&nbsp;Re: About the comment of org.apache.tomcat.util.threads.TaskQueue



tianshuang,

On 10/23/21 23:44, Poison wrote:
&gt; Tomcat version: 8.5.72
&gt; 
&gt; 
&gt; org.apache.tomcat.util.threads.TaskQueue&amp;nbsp;source code:&amp;nbsp;https://github.com/apache/tomcat/blob/8.5.72/java/org/apache/tomcat/util/threads/TaskQueue.java#L33
&gt; 
&gt; 
&gt; In the comments of the&amp;nbsp;TaskQueue&amp;nbsp;class, it mentions "If you use a normal queue, the executor will spawn threads when there are idle threads and you wont be able to force items onto the queue itself.".&amp;nbsp;But when we use java.util.concurrent.LinkedBlockingQueue, it will be put into the queue by default instead of creating threads. What queue does the normal queue in the comment refer to?

Just looking at the class definition, I think "normal queue" means "an 
instance of another subclass of BlockingQueue<Runnable&gt;".

The TaskQueue class has a "force" method which does something like 
"offer" but behaves differently. Tomcat can use this internally instead 
of having to only use the more limited "offer" method of the Queue 
interface.

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org

Re: About the comment of org.apache.tomcat.util.threads.TaskQueue

Posted by Christopher Schultz <ch...@christopherschultz.net>.
tianshuang,

On 10/23/21 23:44, Poison wrote:
> Tomcat version: 8.5.72
> 
> 
> org.apache.tomcat.util.threads.TaskQueue&nbsp;source code:&nbsp;https://github.com/apache/tomcat/blob/8.5.72/java/org/apache/tomcat/util/threads/TaskQueue.java#L33
> 
> 
> In the comments of the&nbsp;TaskQueue&nbsp;class, it mentions "If you use a normal queue, the executor will spawn threads when there are idle threads and you wont be able to force items onto the queue itself.".&nbsp;But when we use java.util.concurrent.LinkedBlockingQueue, it will be put into the queue by default instead of creating threads. What queue does the normal queue in the comment refer to?

Just looking at the class definition, I think "normal queue" means "an 
instance of another subclass of BlockingQueue<Runnable>".

The TaskQueue class has a "force" method which does something like 
"offer" but behaves differently. Tomcat can use this internally instead 
of having to only use the more limited "offer" method of the Queue 
interface.

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org