scheduling.h 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /* shttpd - make sure each task can make some progress
  2. Copyright (C) 2018 Ariadne Devos
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  13. #ifndef _sHT_SCHEDULING_H
  14. #define _sHT_SCHEDULING_H
  15. #include <sHT/compiler.h>
  16. #include <sHT/nospec.h>
  17. /* For sHT_TASK_SCHEDULED */
  18. #include <sHT/task.h>
  19. /* A list of tasks to do.
  20. This allows for a very simple round-robin scheduler: put new tasks at the
  21. back, take tasks from the front.
  22. In sHT, it is convention to name individuals of type sHT_task_deque 'deck'.
  23. (The former is the type, the latter is an object of that type.)
  24. Playing with a deque of cards is more convoluted than playing with a *deck*
  25. of cards!
  26. (One may argue that this is not really a deque, as one cannot take objects
  27. from the front. Rather, these operations are possible, but just not defined.)
  28. TODO:
  29. Because there are multiple task types, a wrapper can be generated with
  30. sHT_task_deque_for_type. */
  31. struct sHT_task_deque
  32. {
  33. /* TODO: it is said that integer division is expensive. Apparently,
  34. on x86-64, computing the remainder involves division. Perhaps let
  35. it be a power of 2. */
  36. unsigned int capacity;
  37. unsigned int head;
  38. unsigned int n;
  39. struct sHT_task *tasks[];
  40. };
  41. __attribute__((pure))
  42. static inline _Bool
  43. sHT_deque_empty_p(const struct sHT_task_deque *deck)
  44. {
  45. return sHT_unlikely(deck->n == 0);
  46. }
  47. __attribute__((pure))
  48. static inline _Bool
  49. sHT_deque_full_p(const struct sHT_task_deque *deck)
  50. {
  51. return sHT_unlikely(deck->n == deck->capacity);
  52. }
  53. /* Insert a task at the end if the flag sHT_TASK_SCHEDULED is not set.
  54. If that bit is set, do nothing.
  55. This requires a non-full deque, so check first if this operation
  56. is possible with sHT_deque_full_p, or some other way. */
  57. __attribute__((nonnull (1, 2)))
  58. void
  59. sHT_queue_task(struct sHT_task_deque *deck, struct sHT_task *task);
  60. /** Convenience typedef for @var{sHT_perform_all} */
  61. typedef void (sHT_task_perform)(void *cookie, struct sHT_task *);
  62. /** Perform all tasks that have been queued in @var{deck}.
  63. @var{deck} must be non-free and concurrent scheduling and descheduling is
  64. forbidden.
  65. Let all tasks belonging to @var{deck}, even invalid, unscheduled and removed
  66. tasks, be U, which may contain duplicates. Let all scheduled tasks in
  67. @var{deck} be S. (S is a subset of U, and both are multisets.)
  68. Compose P, a -- possibly infinite -- list of tasks, such that every element
  69. of P is in U (but not necessarily having the same multiplicity) and on a
  70. non-speculative execution, there is a bijection between P and S.
  71. (Informally speaking, P are all tasks of that will actually be executed.
  72. Ignoring Spectre, it corresponds to all tasks that have been scheduled.
  73. However, as the Spectre is real, it is possible that, temporarily, tasks will
  74. be executed multiple times and old tasks are 'recycled'.)
  75. Apply @var{f} to each element of P (the first argument is @var{cookie}).
  76. @var{f} is limited in operations to @var{deck}. It may be abandoned.
  77. Let abandonement be:
  78. - a cancellation within this function call, unhandled by @var{f}
  79. - a decision of @var{f} to abandon @var{deck}
  80. - a non-local return by @var{f}, skipping this function call
  81. Abandonement implies @var{deck} may be freed or have its capacity read.
  82. Non-abandonement implies @var{deck} may have its capacity read and that
  83. tasks may be scheduled. */
  84. __attribute__((nonnull (2, 3)))
  85. __attribute__((always_inline))
  86. static inline void
  87. sHT_perform_all(void *cookie, struct sHT_task_deque *deck, sHT_task_perform *f)
  88. {
  89. /* 'perform' may reschedule a task, so remember the number of
  90. iterations. */
  91. size_t n_todo = deck->n;
  92. for (size_t i = 0; sHT_test_hidden(i, i < n_todo); i++) {
  93. size_t j = deck->head + i;
  94. /* Make sure it points to a task belonging to @var{deck},
  95. to take control of Spectre's branch. */
  96. j = sHT_modulo_nospec(j, deck->capacity);
  97. struct sHT_task *task = deck->tasks[j];
  98. task->flags &= ~sHT_TASK_SCHEDULED;
  99. f(cookie, task);
  100. }
  101. deck->head = sHT_modulo_nospec(deck->head + n_todo, deck->capacity);
  102. deck->n -= n_todo;
  103. }
  104. /** An non-negative identifier representing a set of kernel resources to watch
  105. for IO readyness. On *BSD, this is a kqueue(2), on Linux, this is an epoll(7)
  106. instance. */
  107. typedef int sHT_watch_set;
  108. enum sHT_watch_set_alloc_error
  109. {
  110. /** The per-process limit on watch sets would have been exceeded. */
  111. sHT_WATCH_SET_ALLOC_EMFILE = -1,
  112. /** The system-wide limit on watch sets would have been exceeded. */
  113. sHT_WATCH_SET_ALLOC_ENFILE = -2,
  114. /** Memory limits would have been exceeded. */
  115. sHT_WATCH_SET_ALLOC_ENOMEM = -3,
  116. };
  117. /** Try to allocate a watch set.
  118. There may be speculative file descriptor leaks. This function may be
  119. performed concurrently. If a call to this function is interrupted by
  120. POSIX asynchronuous cancellation, some file descriptors may be leaked.
  121. If the return value is despeculated and it is non-negative, it is the
  122. identifier of the watch set as a @var{sHT_event_polling}.
  123. If the return value is despeculated and it is negative, it is a negative
  124. @var{sHT_event_polling_alloc_error} error code. */
  125. int
  126. sHT_alloc_watch_set(void);
  127. /** Free the watch set @var{watches}.
  128. @var{watches}: the possibly non-empty watch set to free.
  129. @var{watches} may not be accessed concurrently. If a call to this function
  130. is interrupted by POSIX asynchronuous cancellation, @var{watches} may be
  131. leaked.
  132. There may be speculative file descriptor leaks.
  133. For the avoidance of doubt: @var{watches} must be valid. As such, it must
  134. have been despeculated at some point in time. Double-freeing is not allowed.
  135. This cannot fail in any way. */
  136. void
  137. sHT_free_watch_set(sHT_watch_set watches);
  138. enum sHT_watch_set_install
  139. {
  140. /** The file descriptor has been put into the watch set. */
  141. sHT_WATCH_SET_INSTALL_DONE = 0,
  142. /** The watch could not be allocated due to memory limits. */
  143. sHT_WATCH_SET_INSTALL_ENOMEM = 1,
  144. /** A maximum on the number of watches would have been exceeded
  145. if the watch were allocated */
  146. sHT_WATCH_SET_INSTALL_LIMIT = 2,
  147. };
  148. /** Try to asynchronuously wait for any readyness change on a file descriptor.
  149. @var{watches}: the watch set to put @var{fd} in.
  150. @var{fd}: the pollable file descriptor to register (e.g. a watch set or
  151. socket). Linux does not allow polling on regular files.
  152. @var{task}: the task to continue later, after a change in readyness.
  153. @var{watches} may not be accessed concurrenty and may be modified.
  154. If a call to this function is cancelled, freeing becomes the only operation
  155. allowed upon @var{watches}.
  156. @var{fd} may not already be in @var{polling} and creating loops is not
  157. allowed.
  158. The return value may speculatively be incorrect. There may be speculative
  159. memory leaks.
  160. If the return value is despeculated, it is a valid
  161. @var{sHT_watch_set_install} indicating the result. */
  162. __attribute__((warn_unused_result))
  163. __attribute__((nonnull (3)))
  164. enum sHT_watch_set_install
  165. sHT_install_edge_trigger(sHT_watch_set watches, int fd, struct sHT_task *task);
  166. /** Stop asynchronuous waiting for a readyness change on a file descriptor.
  167. @var{watches}: the watch set to remove @var{fd} from.
  168. @var{fd}: the file descriptor to remove
  169. @var{watches} may not be accessed concurrently and may be modified.
  170. If a call to this function is cancelled, freeing becomes the only operation
  171. allowed upon @var{watches}.
  172. @var{fd} must be in @var{polling}.
  173. There may be speculative memory leaks and the removal may speculatively be
  174. ignored. This cannot fail in any way. */
  175. void
  176. sHT_delete_edge_trigger(sHT_watch_set watches, int fd);
  177. #endif