process_manager.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /**
  2. * @file process_manager.c
  3. * @author Ambroz Bizjak <ambrop7@gmail.com>
  4. *
  5. * @section LICENSE
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the name of the author nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * @section DESCRIPTION
  30. *
  31. * Module which allows starting and controlling template processes using an imperative
  32. * interface.
  33. *
  34. * Synopsis:
  35. * process_manager()
  36. *
  37. * Description:
  38. * Represents a set of managed processes. Each process has a "name", which is a value
  39. * that uniquely identifies it within its process manager.
  40. * When deinitialization is requested, requests termination of all managed processes
  41. * and waits for all of them to terminate before deinitializing.
  42. * Managed processes can access objects as seen from the process_manager() statement
  43. * via the special _caller object.
  44. *
  45. * Synopsis:
  46. * process_manager::start(name, string template_name, list args)
  47. * process_manager::start(string template_name, list args)
  48. *
  49. * Description:
  50. * Creates a new process from the template named 'template_name', with arguments 'args',
  51. * identified by 'name' within the process manager. If the two-argument form of start() is
  52. * used, the process does not have a name, and cannot be imperatively stopped using
  53. * stop().
  54. * If a process with this name already exists and is not being terminated, does nothing.
  55. * If it exists and is being terminated, it will be restarted using the given parameters
  56. * after it terminates. If the process does not exist, it is created immediately, and the
  57. * immediate effects of the process being created happnen before the immediate effects of
  58. * the start() statement going up.
  59. *
  60. * Synopsis:
  61. * process_manager::stop(name)
  62. *
  63. * Description:
  64. * Initiates termination of the process identified by 'name' within the process manager.
  65. * If there is no such process, or the process is already being terminated, does nothing.
  66. * If the process does exist and is not already being terminated, termination of the
  67. * process is requested, and the immediate effects of the termination request happen
  68. * before the immediate effects of the stop() statement going up.
  69. */
  70. #include <stdlib.h>
  71. #include <string.h>
  72. #include <misc/offset.h>
  73. #include <misc/debug.h>
  74. #include <misc/strdup.h>
  75. #include <misc/balloc.h>
  76. #include <structure/LinkedList1.h>
  77. #include <ncd/NCDModule.h>
  78. #include <ncd/static_strings.h>
  79. #include <ncd/extra/value_utils.h>
  80. #include <generated/blog_channel_ncd_process_manager.h>
  81. #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
  82. #define RETRY_TIME 10000
  83. #define PROCESS_STATE_RUNNING 1
  84. #define PROCESS_STATE_STOPPING 2
  85. #define PROCESS_STATE_RESTARTING 3
  86. #define PROCESS_STATE_RETRYING 4
  87. struct instance {
  88. NCDModuleInst *i;
  89. LinkedList1 processes_list;
  90. int dying;
  91. };
  92. struct process {
  93. struct instance *manager;
  94. LinkedList1Node processes_list_node;
  95. BSmallTimer retry_timer; // running if state=retrying
  96. int state;
  97. NCD_string_id_t template_name;
  98. NCDValMem current_mem;
  99. NCDValSafeRef current_name;
  100. NCDValSafeRef current_args;
  101. NCDValMem next_mem; // next_* if state=restarting
  102. NCDValSafeRef next_name;
  103. NCDValSafeRef next_args;
  104. NCDModuleProcess module_process; // if state!=retrying
  105. };
  106. static struct process * find_process (struct instance *o, NCDValRef name);
  107. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  108. static void process_free (struct process *p);
  109. static void process_try (struct process *p);
  110. static void process_retry_timer_handler (BSmallTimer *retry_timer);
  111. static void process_module_process_handler_event (NCDModuleProcess *module_process, int event);
  112. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object);
  113. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
  114. static void process_stop (struct process *p);
  115. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  116. static void instance_free (struct instance *o);
  117. static struct process * find_process (struct instance *o, NCDValRef name)
  118. {
  119. ASSERT(!NCDVal_IsInvalid(name))
  120. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  121. while (n) {
  122. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  123. ASSERT(p->manager == o)
  124. if (!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) && NCDVal_Compare(NCDVal_FromSafe(&p->current_mem, p->current_name), name) == 0) {
  125. return p;
  126. }
  127. n = LinkedList1Node_Next(n);
  128. }
  129. return NULL;
  130. }
  131. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  132. {
  133. ASSERT(!o->dying)
  134. ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)) || !find_process(o, NCDVal_FromSafe(mem, name)))
  135. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  136. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  137. // allocate structure
  138. struct process *p = BAlloc(sizeof(*p));
  139. if (!p) {
  140. ModuleLog(o->i, BLOG_ERROR, "BAlloc failed");
  141. goto fail0;
  142. }
  143. // set manager
  144. p->manager = o;
  145. // insert to processes list
  146. LinkedList1_Append(&o->processes_list, &p->processes_list_node);
  147. // init retry timer
  148. BSmallTimer_Init(&p->retry_timer, process_retry_timer_handler);
  149. // init template name
  150. p->template_name = ncd_get_string_id(NCDVal_FromSafe(mem, template_name), o->i->params->iparams->string_index);
  151. if (p->template_name < 0) {
  152. ModuleLog(o->i, BLOG_ERROR, "ncd_get_string_id failed");
  153. goto fail1;
  154. }
  155. // init current mem as a copy of mem
  156. if (!NCDValMem_InitCopy(&p->current_mem, mem)) {
  157. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  158. goto fail1;
  159. }
  160. // remember name and args
  161. p->current_name = name;
  162. p->current_args = args;
  163. // try starting it
  164. process_try(p);
  165. return 1;
  166. fail1:
  167. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  168. BFree(p);
  169. fail0:
  170. return 0;
  171. }
  172. static void process_free (struct process *p)
  173. {
  174. struct instance *o = p->manager;
  175. // free current mem
  176. NCDValMem_Free(&p->current_mem);
  177. // free timer
  178. BReactor_RemoveSmallTimer(o->i->params->iparams->reactor, &p->retry_timer);
  179. // remove from processes list
  180. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  181. // free structure
  182. BFree(p);
  183. }
  184. static void process_try (struct process *p)
  185. {
  186. struct instance *o = p->manager;
  187. ASSERT(!o->dying)
  188. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  189. ModuleLog(o->i, BLOG_INFO, "trying process");
  190. // init module process
  191. if (!NCDModuleProcess_InitId(&p->module_process, o->i, p->template_name, NCDVal_FromSafe(&p->current_mem, p->current_args), process_module_process_handler_event)) {
  192. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  193. goto fail;
  194. }
  195. // set special objects function
  196. NCDModuleProcess_SetSpecialFuncs(&p->module_process, process_module_process_func_getspecialobj);
  197. // set state
  198. p->state = PROCESS_STATE_RUNNING;
  199. return;
  200. fail:
  201. // set timer
  202. BReactor_SetSmallTimer(o->i->params->iparams->reactor, &p->retry_timer, BTIMER_SET_RELATIVE, RETRY_TIME);
  203. // set state
  204. p->state = PROCESS_STATE_RETRYING;
  205. }
  206. static void process_retry_timer_handler (BSmallTimer *retry_timer)
  207. {
  208. struct process *p = UPPER_OBJECT(retry_timer, struct process, retry_timer);
  209. struct instance *o = p->manager;
  210. B_USE(o)
  211. ASSERT(p->state == PROCESS_STATE_RETRYING)
  212. ASSERT(!o->dying)
  213. // retry
  214. process_try(p);
  215. }
  216. void process_module_process_handler_event (NCDModuleProcess *module_process, int event)
  217. {
  218. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  219. struct instance *o = p->manager;
  220. ASSERT(p->state != PROCESS_STATE_RETRYING)
  221. ASSERT(p->state != PROCESS_STATE_RESTARTING || !o->dying)
  222. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  223. switch (event) {
  224. case NCDMODULEPROCESS_EVENT_UP: {
  225. ASSERT(p->state == PROCESS_STATE_RUNNING)
  226. } break;
  227. case NCDMODULEPROCESS_EVENT_DOWN: {
  228. ASSERT(p->state == PROCESS_STATE_RUNNING)
  229. // allow process to continue
  230. NCDModuleProcess_Continue(&p->module_process);
  231. } break;
  232. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  233. ASSERT(p->state == PROCESS_STATE_RESTARTING || p->state == PROCESS_STATE_STOPPING)
  234. // free module process
  235. NCDModuleProcess_Free(&p->module_process);
  236. if (p->state == PROCESS_STATE_RESTARTING) {
  237. // free current mem
  238. NCDValMem_Free(&p->current_mem);
  239. // move next mem/values over current mem/values
  240. p->current_mem = p->next_mem;
  241. p->current_name = p->next_name;
  242. p->current_args = p->next_args;
  243. // try starting it again
  244. process_try(p);
  245. return;
  246. }
  247. // free process
  248. process_free(p);
  249. // if manager is dying and there are no more processes, let it die
  250. if (o->dying && LinkedList1_IsEmpty(&o->processes_list)) {
  251. instance_free(o);
  252. }
  253. } break;
  254. }
  255. }
  256. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object)
  257. {
  258. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  259. ASSERT(p->state != PROCESS_STATE_RETRYING)
  260. if (name == NCD_STRING_CALLER) {
  261. *out_object = NCDObject_Build(-1, p, NCDObject_no_getvar, process_module_process_caller_obj_func_getobj);
  262. return 1;
  263. }
  264. return 0;
  265. }
  266. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
  267. {
  268. struct process *p = NCDObject_DataPtr(obj);
  269. struct instance *o = p->manager;
  270. ASSERT(p->state != PROCESS_STATE_RETRYING)
  271. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  272. }
  273. static void process_stop (struct process *p)
  274. {
  275. switch (p->state) {
  276. case PROCESS_STATE_RETRYING: {
  277. // free process
  278. process_free(p);
  279. } break;
  280. case PROCESS_STATE_RUNNING: {
  281. // request process to terminate
  282. NCDModuleProcess_Terminate(&p->module_process);
  283. // set state
  284. p->state = PROCESS_STATE_STOPPING;
  285. } break;
  286. case PROCESS_STATE_RESTARTING: {
  287. // free next mem
  288. NCDValMem_Free(&p->next_mem);
  289. // set state
  290. p->state = PROCESS_STATE_STOPPING;
  291. } break;
  292. case PROCESS_STATE_STOPPING: {
  293. // nothing to do
  294. } break;
  295. default: ASSERT(0);
  296. }
  297. }
  298. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  299. {
  300. struct instance *o = p->manager;
  301. ASSERT(!o->dying)
  302. ASSERT(p->state == PROCESS_STATE_STOPPING)
  303. ASSERT(!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)))
  304. ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_Compare(NCDVal_FromSafe(mem, name), NCDVal_FromSafe(&p->current_mem, p->current_name)) == 0)
  305. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  306. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  307. // copy mem to next mem
  308. if (!NCDValMem_InitCopy(&p->next_mem, mem)) {
  309. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  310. goto fail0;
  311. }
  312. // remember name and args to next
  313. p->next_name = name;
  314. p->next_args = args;
  315. // set state
  316. p->state = PROCESS_STATE_RESTARTING;
  317. return 1;
  318. fail0:
  319. return 0;
  320. }
  321. static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  322. {
  323. struct instance *o = vo;
  324. o->i = i;
  325. // check arguments
  326. if (!NCDVal_ListRead(params->args, 0)) {
  327. ModuleLog(o->i, BLOG_ERROR, "wrong arity");
  328. goto fail0;
  329. }
  330. // init processes list
  331. LinkedList1_Init(&o->processes_list);
  332. // set not dying
  333. o->dying = 0;
  334. // signal up
  335. NCDModuleInst_Backend_Up(o->i);
  336. return;
  337. fail0:
  338. NCDModuleInst_Backend_DeadError(i);
  339. }
  340. void instance_free (struct instance *o)
  341. {
  342. ASSERT(LinkedList1_IsEmpty(&o->processes_list))
  343. NCDModuleInst_Backend_Dead(o->i);
  344. }
  345. static void func_die (void *vo)
  346. {
  347. struct instance *o = vo;
  348. ASSERT(!o->dying)
  349. // request all processes to die
  350. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  351. while (n) {
  352. LinkedList1Node *next = LinkedList1Node_Next(n);
  353. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  354. process_stop(p);
  355. n = next;
  356. }
  357. // if there are no processes, die immediately
  358. if (LinkedList1_IsEmpty(&o->processes_list)) {
  359. instance_free(o);
  360. return;
  361. }
  362. // set dying
  363. o->dying = 1;
  364. }
  365. static void start_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  366. {
  367. // check arguments
  368. NCDValRef name_arg = NCDVal_NewInvalid();
  369. NCDValRef template_name_arg;
  370. NCDValRef args_arg;
  371. if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg) &&
  372. !NCDVal_ListRead(params->args, 3, &name_arg, &template_name_arg, &args_arg)
  373. ) {
  374. ModuleLog(i, BLOG_ERROR, "wrong arity");
  375. goto fail0;
  376. }
  377. if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
  378. ModuleLog(i, BLOG_ERROR, "wrong type");
  379. goto fail0;
  380. }
  381. // signal up.
  382. // Do it before creating the process so that the process starts initializing before our own process continues.
  383. NCDModuleInst_Backend_Up(i);
  384. // get method object
  385. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  386. if (mo->dying) {
  387. ModuleLog(i, BLOG_INFO, "manager is dying, not creating process");
  388. } else {
  389. struct process *p = (NCDVal_IsInvalid(name_arg) ? NULL : find_process(mo, name_arg));
  390. if (p && p->state != PROCESS_STATE_STOPPING) {
  391. ModuleLog(i, BLOG_INFO, "process already started");
  392. } else {
  393. if (p) {
  394. if (!process_restart(p, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  395. ModuleLog(i, BLOG_ERROR, "failed to restart process");
  396. goto fail0;
  397. }
  398. } else {
  399. if (!process_new(mo, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  400. ModuleLog(i, BLOG_ERROR, "failed to create process");
  401. goto fail0;
  402. }
  403. }
  404. }
  405. }
  406. return;
  407. fail0:
  408. NCDModuleInst_Backend_DeadError(i);
  409. }
  410. static void stop_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  411. {
  412. // check arguments
  413. NCDValRef name_arg;
  414. if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
  415. ModuleLog(i, BLOG_ERROR, "wrong arity");
  416. goto fail0;
  417. }
  418. // signal up.
  419. // Do it before stopping the process so that the process starts terminating before our own process continues.
  420. NCDModuleInst_Backend_Up(i);
  421. // get method object
  422. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  423. if (mo->dying) {
  424. ModuleLog(i, BLOG_INFO, "manager is dying, not stopping process");
  425. } else {
  426. struct process *p = find_process(mo, name_arg);
  427. if (!(p && p->state != PROCESS_STATE_STOPPING)) {
  428. ModuleLog(i, BLOG_INFO, "process already stopped");
  429. } else {
  430. process_stop(p);
  431. }
  432. }
  433. return;
  434. fail0:
  435. NCDModuleInst_Backend_DeadError(i);
  436. }
  437. static struct NCDModule modules[] = {
  438. {
  439. .type = "process_manager",
  440. .func_new2 = func_new,
  441. .func_die = func_die,
  442. .alloc_size = sizeof(struct instance)
  443. }, {
  444. .type = "process_manager::start",
  445. .func_new2 = start_func_new
  446. }, {
  447. .type = "process_manager::stop",
  448. .func_new2 = stop_func_new
  449. }, {
  450. .type = NULL
  451. }
  452. };
  453. const struct NCDModuleGroup ncdmodule_process_manager = {
  454. .modules = modules
  455. };