.. Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed
   with this work for additional information regarding copyright
   ownership.  The ASF licenses this file to you under the Apache
   License, Version 2.0 (the "License"); you may not use this file
   except in compliance with the License.  You may obtain a copy of
   the License at

   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied.  See the License for the specific language governing
   permissions and limitations under the License.

.. include:: ../../../common.defs

.. default-domain:: cpp

TSContScheduleOnPool
********************

Synopsis
========

.. code-block:: cpp

    #include <ts/ts.h>

.. function:: TSAction TSContScheduleOnPool(TSCont contp, TSHRTime timeout, TSThreadPool tp)

Description
===========

Schedules :arg:`contp` to run :arg:`timeout` milliseconds in the future, on a random thread that
belongs to :arg:`tp`. The :arg:`timeout` is an approximation, meaning it will be at least
:arg:`timeout` milliseconds but possibly more. Resolutions finer than roughly 5 milliseconds will
not be effective. Note that :arg:`contp` is required to have a mutex, which is provided to
:func:`TSContCreate`.

The continuation is scheduled for a particular thread selected from a group of similar threads,
as indicated by :arg:`tp`. If :arg:`contp` already has an thread affinity set, and the thread
type of thread affinity is the same as :arg:`tp`, then :arg:`contp` will be scheduled on the
thread specified by thread affinity.

=========================== =======================================================================================
Pool                        Properties
=========================== =======================================================================================
``TS_THREAD_POOL_NET``      Transaction processing threads. Continuations on these threads must not block.
``TS_THREAD_POOL_TASK``     Background threads. Continuations can perform blocking operations.
``TS_THREAD_POOL_DNS``      DNS request processing. May not exist depending on configuration. Not recommended.
``TS_THREAD_POOL_UDP``      UDP processing.
=========================== =======================================================================================

In practice, any choice except ``TS_THREAD_POOL_NET`` or ``TS_THREAD_POOL_TASK`` is strongly not
recommended. The ``TS_THREAD_POOL_NET`` threads are the same threads on which callback hooks are
called and continuations that use them have the same restrictions. ``TS_THREAD_POOL_TASK`` threads
are threads that exist to perform long or blocking actions, although sufficiently long operation can
impact system performance by blocking other continuations on the threads.

The return value can be used to cancel the scheduled event via :func:`TSActionCancel`. This is
effective until the continuation :arg:`contp` is being dispatched. However, if it is scheduled on
another thread this can be problematic to be correctly timed. The return value can be checked with
:func:`TSActionDone` to see if the continuation ran before the return, which is possible if
:arg:`timeout` is `0`.

If :arg:`contp` has no thread affinity set, the thread it is now scheduled on will be set
as its thread affinity thread.

Note that the TSContSchedule() family of API shall only be called from an ATS EThread.
Calling it from raw non-EThreads can result in unpredictable behavior.

Note that some times if the TASK threads are not ready when you schedule a "contp" on the  ``TS_THREAD_POOL_TASK`` then
the "contp" could end up being executed in a ``ET_NET`` thread instead, more likely this is not what you want.
To avoid this you can use the ``TS_LIFECYCLE_TASK_THREADS_READY_HOOK`` to make sure that the task threads
have been started when you schedule a "contp". You can refer to :func:`TSLifecycleHookAdd` for more details.

Example Scenarios
=================

Scenario 1 (no thread affinity info, different types of threads)
----------------------------------------------------------------

When thread affinity is not set, a plugin calls the API on thread "A" (which is an "ET_TASK" type), and
wants to schedule on an "ET_NET" type thread provided in "tp", the system would see there is no thread
affinity information stored in "contp."

In this situation, system sees there is no thread affinity information stored in "contp". It then
checks whether the type of thread "A" is the same as provided in "tp", and sees that "A" is "ET_TASK",
but "tp" says "ET_NET". So "contp" gets scheduled on the next available "ET_NET" thread provided by a
round robin list, which we will call thread "B". Since "contp" doesn't have thread affinity information,
thread "B" will be assigned as the affinity thread for it automatically.

The reason for doing this is most of the time people want to schedule the same things on the same type
of thread, so logically it is better to default the first thread that it is scheduled on as the affinity
thread.

Scenario 2 (no thread affinity info, same types of threads)
-----------------------------------------------------------

Slight variation of scenario 1, instead of scheduling on a "ET_NET" thread, the plugin wants to schedule
on a "ET_TASK" thread (i.e. "tp" contains "ET_TASK" now), all other conditions stays the same.

This time since the type of the desired thread for scheduling and thread "A" are the same, the system
schedules "contp" on thread "A", and assigns thread "A" as the affinity thread for "contp".

The reason behind this choice is that we are trying to keep things simple such that lock contention
problems happens less. And for the most part, there is no point of scheduling the same thing on several
different threads of the same type, because there is no parallelism between them (a thread will have to
wait for the previous thread to finish, either because locking or the nature of the job it's handling is
serialized since its on the same continuation).

Scenario 3 (has thread affinity info, different types of threads)
-----------------------------------------------------------------

Slight variation of scenario 1, thread affinity is set for continuation "contp" to thread "A", all other
conditions stays the same.

In this situation, the system sees that the "tp" has "ET_NET", but the type of thread "A" is "ET_TASK".
So even though "contp" has an affinity thread, the system will not use that information since the type is
different, instead it schedules "contp" on the next available "ET_NET" thread provided by a round robin
list, which we will call thread "B". The difference with scenario 1 is that since thread "A" is set to
be the affinity thread for "contp" already, the system will NOT overwrite that information with thread "B".

Most of the time, a continuation will be scheduled on one type of threads, and rarely gets scheduled on
a different type. But when that happens, we want it to return to the thread it was previously on, so it
won't have any lock contention problems. And that's also why "thread_affinity" is not a hashmap of thread
types and thread pointers.

Scenario 4 (has thread affinity info, same types of threads)
------------------------------------------------------------

Slight variation of scenario 3, the only difference is "tp" now says "ET_TASK".

This is the easiest scenario since the type of thread "A" and "tp" are the same, so the system schedules
"contp" on thread "A". And, as discussed, there is really no reason why one may want to schedule
the same continuation on two different threads of the same type.

.. note:: In scenario 3 & 4, it doesn't matter which thread the plugin is calling the API from.


See Also
========

:doc:`TSContScheduleEveryOnPool.en`
:doc:`TSContScheduleOnThread.en`
:doc:`TSLifecycleHookAdd.en`
