Merge lp:~victored/cerbere/improvements into lp:~elementary-pantheon/cerbere/cerbere

Proposed by Victor Martinez
Status: Merged
Approved by: Allen Lowe
Approved revision: 24
Merged at revision: 21
Proposed branch: lp:~victored/cerbere/improvements
Merge into: lp:~elementary-pantheon/cerbere/cerbere
Diff against target: 618 lines (+329/-195)
8 files modified
INSTALL (+2/-1)
org.pantheon.cerbere.gschema.xml (+13/-15)
src/CMakeLists.txt (+9/-4)
src/Cerbere.vala (+33/-50)
src/ProcessInfo.vala (+152/-0)
src/ProcessTimer.vala (+0/-48)
src/SettingsManager.vala (+52/-0)
src/Watchdog.vala (+68/-77)
To merge this branch: bzr merge lp:~victored/cerbere/improvements
Reviewer Review Type Date Requested Status
Sergey "Shnatsel" Davidoff (community) Approve
Review via email: mp+109762@code.launchpad.net

Description of the change

I've been testing the performance and reliability of the current Cerbere and I'm not satisfied with the results. Here's what I found:

1) Some processes are not respawned correctly. For instance, wingpanel is not even respawned most of the time!
2) Processes are spawned sequentially (this increases the startup time of the Pantheon desktop.)
3) The process' crash-count is increased even if the crashes didn't occur consecutively, resulting in that process not being monitored anymore. This is likely to happen after many days of activity.
4) No support for command-line arguments. This prevents us from adding stuff like 'slingshot --silent' to the process monitor.
5) Settings are only read during startup. If you change something, you need to restart cerbere (for most users this is a "logout" -> "login" operation) in order to use the new settings. What should happen:
   - The keys "crash-time" and "crash-time-interval" should be always in sync with the app. If you change their values, cerbere should start using the new values right away.
   - If the values of "desktop-components" are changed, cerbere should apply immediately the new settings. This means:
     - If a program was removed from the list, stop monitoring it right away
     - If a new program was added to the list, launch it and start monitoring it immediately

While these issues don't seem to be important, they certainly are when you have your computer turned on for more than 10 days (based on the crash frequency of some components).

Other issues:
6) Settings are located under org.pantheon.cerbere.settings instead of just org.pantheon.cerbere
7) Cerbere is more like a multi-purpose app, since you can pretty much add anything to it. Therefore, the processes key should be renamed from "desktop-components" to something like "monitored-proceses"
8) Inconvenient variant types on keys "crash-time" and "max-crashes". (Should be uint instead of int, since setting negative values breaks cerbere)

This branch fixes all of the issues mentioned above.

To post a comment you must log in.
Revision history for this message
Sergey "Shnatsel" Davidoff (shnatsel) wrote :

This is great! I've been looking forward to this for so long! I've copied the older Cerbere into testing PPA just in case this version breaks anything. Let's merge this ASAP!

review: Approve
Revision history for this message
Victor Martinez (victored) wrote :

Thanks for the review! :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'INSTALL'
2--- INSTALL 2012-04-29 01:11:54 +0000
3+++ INSTALL 2012-06-12 01:47:19 +0000
4@@ -1,5 +1,6 @@
5+# cd to the cerbere folder
6 mkdir build
7 cd build
8-cmake ..
9+cmake .. -DCMAKE_INSTALL_PREFIX=/usr
10 make
11 sudo make install
12
13=== modified file 'org.pantheon.cerbere.gschema.xml'
14--- org.pantheon.cerbere.gschema.xml 2012-04-30 19:18:34 +0000
15+++ org.pantheon.cerbere.gschema.xml 2012-06-12 01:47:19 +0000
16@@ -1,22 +1,20 @@
17 <?xml version="1.0" encoding="UTF-8"?>
18 <schemalist>
19- <schema path="/org/pantheon/cerbere/settings/" id="org.pantheon.cerbere.settings" gettext-domain="cerbere">
20- <key type="as" name="desktop-components">
21+ <schema path="/org/pantheon/cerbere/" id="org.pantheon.cerbere" gettext-domain="cerbere">
22+ <key type="as" name="monitored-processes">
23 <default>['wingpanel','plank','/usr/lib/notify-osd/notify-osd']</default>
24- <summary>List of desktop components</summary>
25- <description>These applications will be launched and watched by cerbere and autorestarted automagically</description>
26+ <summary>List of monitored components</summary>
27+ <description>These applications will be launched and watched by Cerbere, and autorestarted automagically in case they exit</description>
28 </key>
29-
30- <key type="i" name="max-crashes">
31+ <key type="u" name="max-crashes">
32 <default>5</default>
33- <summary>Maximum permitted times to relaunch a process which crashes</summary>
34- <description>Maximum permitted times to relaunch a process which crashes. Cerbere will stop trying after this number is reached.</description>
35- </key>
36-
37- <key type="d" name="crash-time">
38- <default>10.0</default>
39- <summary>Time in which a process is considered crashed</summary>
40- <description>(in seconds)</description>
41- </key>
42+ <summary>Maximum consecutive crashes permitted</summary>
43+ <description>Maximum consecutive crashes permitted. Cerbere will stop trying after this value is reached.</description>
44+ </key>
45+ <key type="u" name="crash-time-interval">
46+ <default>10000</default>
47+ <summary>Time in which a process is considered crashed (in milliseconds, 1/1000ths of a second)</summary>
48+ <description>If a process is started, and then it exits in less time than the value of this key, it is considered crashed.</description>
49+ </key>
50 </schema>
51 </schemalist>
52
53=== modified file 'src/CMakeLists.txt'
54--- src/CMakeLists.txt 2012-02-11 21:34:28 +0000
55+++ src/CMakeLists.txt 2012-06-12 01:47:19 +0000
56@@ -2,10 +2,9 @@
57
58 find_package(Vala REQUIRED)
59 include(ValaVersion)
60-ensure_vala_version("0.10" MINIMUM)
61+ensure_vala_version("0.14" MINIMUM)
62 include(ValaPrecompile)
63
64-
65 # pkgconfig, real C code
66 find_package(PkgConfig)
67 pkg_check_modules(DEPS REQUIRED glib-2.0 gio-2.0 gee-1.0)
68@@ -19,13 +18,19 @@
69
70
71 add_definitions(${CFLAGS})
72-vala_precompile(VALA_C Cerbere.vala Watchdog.vala ProcessTimer.vala
73+vala_precompile (
74+VALA_C
75+ Cerbere.vala
76+ SettingsManager.vala
77+ Watchdog.vala
78+ ProcessInfo.vala
79 PACKAGES
80 glib-2.0
81 gio-2.0
82 gee-1.0
83 OPTIONS
84- --thread)
85+ --target-glib=2.32
86+ ${ADD_OPTIONS})
87
88 add_executable(cerbere
89 ${VALA_C} )
90
91=== modified file 'src/Cerbere.vala'
92--- src/Cerbere.vala 2012-04-30 19:18:34 +0000
93+++ src/Cerbere.vala 2012-06-12 01:47:19 +0000
94@@ -1,3 +1,4 @@
95+/* -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- */
96 /*
97 * Cerbere.vala
98 * This file is part of cerbere, a watchdog for the Pantheon Desktop
99@@ -21,59 +22,41 @@
100 *
101 * Authors: Allen Lowe <allen@elementaryos.org>
102 * ammonkey <am.monkeyd@gmail.com>
103+ * Victor Eduardo <victoreduardm@gmail.com>
104 */
105
106-using GLib;
107-
108-namespace Cerbere {
109-
110- public class Cerbere : Application {
111-
112+public class Cerbere : GLib.Application {
113+
114+ public static SettingsManager settings { get; private set; default = null; }
115 private Watchdog watchdog;
116
117- private double crash_time;
118- private int max_crashes;
119-
120- private string[] desktop_bins;
121-
122- construct {
123- application_id = "org.pantheon.cerbere";
124- flags = GLib.ApplicationFlags.IS_SERVICE;
125- }
126-
127- protected override void startup () {
128- load_config ();
129-
130- // Start watchdog
131- watchdog = new Watchdog (crash_time, max_crashes);
132-
133- start_desktop ();
134-
135- var main_loop = new MainLoop ();
136- main_loop.run ();
137- }
138-
139- void load_config () {
140- var settings = new Settings ("org.pantheon.cerbere.settings");
141- desktop_bins = settings.get_strv ("desktop-components");
142- max_crashes = settings.get_int ("max-crashes");
143- crash_time = settings.get_double ("crash-time");
144- }
145-
146- void start_desktop () {
147- foreach (string bin in desktop_bins) {
148- if (bin != null) {
149- watchdog.watch_process (bin);
150- }
151- }
152- }
153-
154- public static int main (string[] args) {
155- var app = new Cerbere ();
156- app.run (args);
157-
158- return 0;
159- }
160+ construct {
161+ application_id = "org.pantheon.cerbere";
162+ flags = GLib.ApplicationFlags.IS_SERVICE;
163+ }
164+
165+ protected override void startup () {
166+ this.settings = new SettingsManager ();
167+
168+ // Start watchdog
169+ this.watchdog = new Watchdog ();
170+ this.start_processes (this.settings.process_list);
171+
172+ // Monitor changes
173+ this.settings.process_list_changed.connect (this.start_processes);
174+
175+ var main_loop = new MainLoop ();
176+ main_loop.run ();
177+ }
178+
179+ private void start_processes (string[] process_list) {
180+ foreach (string cmd in process_list) {
181+ this.watchdog.add_process_async (cmd);
182+ }
183+ }
184+
185+ public static int main (string[] args) {
186+ var app = new Cerbere ();
187+ return app.run (args);
188 }
189 }
190-
191
192=== added file 'src/ProcessInfo.vala'
193--- src/ProcessInfo.vala 1970-01-01 00:00:00 +0000
194+++ src/ProcessInfo.vala 2012-06-12 01:47:19 +0000
195@@ -0,0 +1,152 @@
196+/* -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- */
197+/*
198+ * Copyright (C) 2012 Victor Eduardo <victoreduardm@gmail.com>
199+ *
200+ * Cerbere is free software; you can redistribute it and/or modify
201+ * it under the terms of the GNU General Public License as published by
202+ * the Free Software Foundation; either version 2 of the License, or
203+ * (at your option) any later version.
204+ *
205+ * Cerbere is distributed in the hope that it will be useful,
206+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
207+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
208+ * GNU General Public License for more details.
209+ *
210+ * You should have received a copy of the GNU General Public License
211+ * along with Cerbere; if not, write to the Free Software
212+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
213+ * Boston, MA 02110-1301 USA
214+ *
215+ * Authors: Victor Eduardo <victoreduardm@gmail.com>
216+ */
217+
218+public class ProcessInfo : GLib.Object {
219+
220+ public signal void exited (bool normal_exit);
221+ //public signal void started ();
222+
223+ public enum Status {
224+ INACTIVE, // not yet spawned
225+ RUNNING, // active (already spawned)
226+ TERMINATED // killed/exited
227+ }
228+
229+ private string command = "";
230+ private GLib.Pid pid = -1;
231+
232+ public Status status = Status.INACTIVE;
233+ public int exit_count { get; private set; default = 0; }
234+ public int crash_count { get; private set; default = 0; }
235+
236+ private GLib.Timer? timer = null;
237+
238+ public ProcessInfo (string command) {
239+ this.command = command;
240+ }
241+
242+ public void reset_crash_count () {
243+ debug ("RESETTING crash count of '%s' to 0 (normal exit)", this.command);
244+ this.crash_count = 0;
245+ }
246+
247+ public async void run_async () {
248+ this.run ();
249+ }
250+
251+ public void run () {
252+ message ("STARTING process: %s", command);
253+
254+ if (this.status == Status.RUNNING) {
255+ message ("PROCESS %s is already running", command);
256+ return;
257+ }
258+
259+ GLib.Pid process_id;
260+
261+ var flags = GLib.SpawnFlags.SEARCH_PATH |
262+ GLib.SpawnFlags.DO_NOT_REAP_CHILD |
263+ GLib.SpawnFlags.STDOUT_TO_DEV_NULL; // discard process output
264+
265+ // parse args
266+ string[] argvp = null;
267+ try {
268+ GLib.Shell.parse_argv (this.command, out argvp);
269+ }
270+ catch (GLib.ShellError error) {
271+ warning ("Not passing any args to %s : %s", this.command, error.message);
272+ argvp = {this.command, null}; // fix value in case it's corrupted
273+ }
274+
275+ if (argvp == null)
276+ return;
277+
278+ // Spawn process asynchronously
279+ try {
280+ GLib.Process.spawn_async (null, argvp, null, flags, null, out process_id);
281+ }
282+ catch (GLib.Error err) {
283+ warning (err.message);
284+ return;
285+ }
286+
287+ // time starts counting here
288+ this.timer = new GLib.Timer ();
289+
290+ this.pid = process_id;
291+ this.status = Status.RUNNING;
292+
293+ // Emit signal
294+ //this.started ();
295+
296+ // Add watch
297+ GLib.ChildWatch.add (this.pid, (pid, status) => {
298+ if (pid != this.pid)
299+ return;
300+
301+ message ("Process '%s' has been closed (ChildWatch exit)", command);
302+ // Check exit status
303+ if (GLib.Process.if_exited (status) || GLib.Process.if_signaled (status) ||
304+ GLib.Process.core_dump (status))
305+ {
306+ this.terminate ();
307+ }
308+ });
309+ }
310+
311+ public void terminate () {
312+ if (this.status != Status.RUNNING)
313+ return;
314+
315+ message ("Process %s is being terminated", command);
316+
317+ GLib.Process.close_pid (this.pid);
318+
319+ bool is_crash = false;
320+
321+ if (this.timer != null) {
322+ this.timer.stop ();
323+
324+ double ellapsed_secs = this.timer.elapsed ();
325+ double crash_time_interval_secs = (double)Cerbere.settings.crash_time_interval / 1000.0;
326+
327+ debug ("Elapsed time = %f secs", ellapsed_secs);
328+ debug ("Min allowed time = %f secs", crash_time_interval_secs);
329+
330+ if (ellapsed_secs <= crash_time_interval_secs) // process crashed
331+ is_crash = true;
332+ }
333+
334+ if (is_crash) {
335+ this.crash_count ++;
336+ message ("Process '%s' crashed", this.command);
337+ }
338+
339+ this.exit_count ++;
340+ this.status = Status.TERMINATED;
341+
342+ this.timer = null;
343+
344+ // Emit signal
345+ this.exited (!is_crash);
346+ }
347+}
348
349=== removed file 'src/ProcessTimer.vala'
350--- src/ProcessTimer.vala 2012-02-11 21:34:28 +0000
351+++ src/ProcessTimer.vala 1970-01-01 00:00:00 +0000
352@@ -1,48 +0,0 @@
353-/*
354- * ProcessTimer.vala
355- * This file is part of cerbere, a watchdog for the Pantheon Desktop
356- *
357- * Copyright (C) 2011-2012 - Allen Lowe
358- *
359- * Cerbere is free software; you can redistribute it and/or modify
360- * it under the terms of the GNU General Public License as published by
361- * the Free Software Foundation; either version 2 of the License, or
362- * (at your option) any later version.
363- *
364- * Cerbere is distributed in the hope that it will be useful,
365- * but WITHOUT ANY WARRANTY; without even the implied warranty of
366- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
367- * GNU General Public License for more details.
368- *
369- * You should have received a copy of the GNU General Public License
370- * along with Cerbere; if not, write to the Free Software
371- * Foundation, Inc., 51 Franklin St, Fifth Floor,
372- * Boston, MA 02110-1301 USA
373- *
374- * Authors: Victor Eduardo <victoreduardm@gmail.com>
375- */
376-
377-public class ProcessTimer {
378-
379- private GLib.Timer timer;
380-
381- public double elapsed {
382- get {
383- return timer.elapsed();
384- }
385- }
386-
387- public ProcessTimer () {
388- // time starts counting here
389- timer = new GLib.Timer ();
390- }
391-
392- public void reset () {
393- timer.start ();
394- }
395-
396- public void stop () {
397- timer.stop ();
398- }
399-}
400-
401
402=== added file 'src/SettingsManager.vala'
403--- src/SettingsManager.vala 1970-01-01 00:00:00 +0000
404+++ src/SettingsManager.vala 2012-06-12 01:47:19 +0000
405@@ -0,0 +1,52 @@
406+/* -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- */
407+/*
408+ * Copyright (C) 2012 Victor Eduardo <victoreduardm@gmail.com>
409+ *
410+ * Cerbere is free software; you can redistribute it and/or modify
411+ * it under the terms of the GNU General Public License as published by
412+ * the Free Software Foundation; either version 2 of the License, or
413+ * (at your option) any later version.
414+ *
415+ * Cerbere is distributed in the hope that it will be useful,
416+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
417+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
418+ * GNU General Public License for more details.
419+ *
420+ * You should have received a copy of the GNU General Public License
421+ * along with Cerbere; if not, write to the Free Software
422+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
423+ * Boston, MA 02110-1301 USA
424+ *
425+ * Authors: Victor Eduardo <victoreduardm@gmail.com>
426+ */
427+
428+public class SettingsManager : GLib.Object {
429+
430+ public signal void process_list_changed (string[] new_values);
431+
432+ static const string SETTINGS_PATH = "org.pantheon.cerbere";
433+
434+ static const string MAX_CRASHES_KEY = "max-crashes";
435+ static const string CRASH_TIME_INTERVAL_KEY = "crash-time-interval";
436+ static const string MONITORED_PROCESSES_KEY = "monitored-processes";
437+
438+ public string[] process_list { get; set; }
439+ public uint max_crashes { get; set; default = 0; }
440+ public uint crash_time_interval { get; set; default = 0; }
441+
442+ private GLib.Settings? settings = null;
443+
444+ public SettingsManager () {
445+ this.settings = new GLib.Settings (SETTINGS_PATH);
446+
447+ this.settings.bind (MAX_CRASHES_KEY, this, "max-crashes", SettingsBindFlags.DEFAULT);
448+ this.settings.bind (CRASH_TIME_INTERVAL_KEY, this, "crash-time-interval", SettingsBindFlags.DEFAULT);
449+ this.settings.bind (MONITORED_PROCESSES_KEY, this, "process-list", SettingsBindFlags.DEFAULT);
450+
451+ this.settings.changed[MONITORED_PROCESSES_KEY].connect (this.on_process_list_changed);
452+ }
453+
454+ private void on_process_list_changed () {
455+ this.process_list_changed (this.settings.get_strv (MONITORED_PROCESSES_KEY));
456+ }
457+}
458
459=== modified file 'src/Watchdog.vala'
460--- src/Watchdog.vala 2012-03-09 05:15:32 +0000
461+++ src/Watchdog.vala 2012-06-12 01:47:19 +0000
462@@ -1,3 +1,4 @@
463+/* -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- */
464 /*
465 * Watchdog.vala
466 * This file is part of cerbere, a watchdog for the Pantheon Desktop
467@@ -24,84 +25,74 @@
468 * Victor Eduardo <victoreduardm@gmail.com>
469 */
470
471-using GLib.Process;
472-
473 public class Watchdog {
474-
475- private double CRASH_TIME;
476- private int MAX_CRASHES;
477-
478- private Gee.HashMap<string, int> pids;
479- private Gee.HashMap<string, ProcessTimer> run_time;
480- private Gee.HashMap<string, int> exit_count;
481- private Gee.HashMap<string, int> crash_count;
482-
483- public Watchdog (double crash_time, int max_crashes) {
484- CRASH_TIME = crash_time;
485- MAX_CRASHES = max_crashes;
486-
487- pids = new Gee.HashMap<string, int> ();
488- run_time = new Gee.HashMap<string, ProcessTimer> ();
489- exit_count = new Gee.HashMap<string, int> ();
490- crash_count = new Gee.HashMap<string, int> ();
491- }
492-
493- public void watch_process (string bin_name) {
494- Pid pid;
495-
496- try {
497- GLib.Process.spawn_async (null, {bin_name, null}, null, SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD | SpawnFlags.STDOUT_TO_DEV_NULL | SpawnFlags.STDERR_TO_DEV_NULL, null, out pid);
498- }
499- catch (GLib.Error err) {
500- warning (err.message);
501- }
502-
503- stdout.printf("\nPROCESS STARTED: [ID = %i] %s", (int)pid, bin_name);
504-
505- pids.set (bin_name, (int)pid);
506-
507- // Check if we have not created the counters before
508-
509- if (!exit_count.has_key(bin_name))
510- exit_count.set (bin_name, 0);
511-
512- if (!crash_count.has_key(bin_name))
513- crash_count.set (bin_name, 0);
514-
515- // Add and run timer
516- run_time.set(bin_name, new ProcessTimer());
517-
518- ChildWatch.add (pid, (a,b) => {
519- on_process_exit (a, b, bin_name);
520+ // Contains ALL the processes that are being monitored
521+ private Gee.HashMap<string, ProcessInfo> processes;
522+ private GLib.Mutex data_lock;
523+
524+ public Watchdog () {
525+ this.processes = new Gee.HashMap<string, ProcessInfo> ();
526+ }
527+
528+ public async void add_process_async (string command) {
529+ this.add_process (command);
530+ }
531+
532+ public void add_process (string command) {
533+ if (command.strip () == "") // whitespace check
534+ return;
535+
536+ // Check if a process for this command has already been created
537+ if (this.processes.has_key (command))
538+ return;
539+
540+ // Create new process
541+ var process = new ProcessInfo (command);
542+
543+ // Add it to the table
544+ this.data_lock.lock ();
545+ this.processes[command] = process;
546+ this.data_lock.unlock ();
547+
548+ // Exit handler. Respawning occurs here
549+ process.exited.connect ( (normal_exit) => {
550+ if (normal_exit) {
551+ // Reset crash count. We only want to count consecutive crashes, so if a normal exit
552+ // is detected, we should reset the counter.
553+ process.reset_crash_count ();
554+ }
555+
556+ // if still in the list, relaunch if possible
557+ if (command in Cerbere.settings.process_list) {
558+ // Check if the process is still present in the table since it could have been removed.
559+ if (processes.has_key (command)) {
560+ // Check if the process already exceeded the maximum number of allowed crashes.
561+ uint max_crashes = Cerbere.settings.max_crashes;
562+ if (process.crash_count <= max_crashes) {
563+ message ("RELAUNCHING: %s", command);
564+ process.run (); // Reload right away
565+ }
566+ else {
567+ message ("'%s' exceeded the maximum number of crashes allowed (%s). It won't be launched again", command, max_crashes.to_string ());
568+ }
569+ }
570+ else {
571+ // If a process is not in the table, it means it wasn't re-launched after it exited, so
572+ // in theory this code is never reached.
573+ message ("You should NEVER get this message. If you're getting it, contact the developers.");
574+ }
575+ }
576+ else {
577+ // Remove from the list. At this point the reference count should
578+ // drop to 0 and free the process.
579+ message ("'%s' is no longer on settings. It will not be monitored anymore", command);
580+ this.data_lock.lock ();
581+ this.processes.unset (command);
582+ this.data_lock.unlock ();
583+ }
584 });
585- }
586-
587-
588- private void on_process_exit (Pid pid, int status, string name) {
589- double elapsed_time = run_time.get(name).elapsed;
590- int exit_times = exit_count.get(name), crashes = crash_count.get(name);
591-
592- stdout.printf("\nPROCESS TERMINATED: [ID = %i] %s [Status = %i]", (int)pid, name, status);
593- stdout.printf(" [Elapsed time = %.2fs]", elapsed_time);
594-
595- exit_count.set(name, ++exit_times);
596- Process.close_pid (pid);
597-
598- stdout.printf(" [Exit times = %i]", exit_times);
599-
600- run_time.unset(name, null);
601- pids.unset (name, null);
602-
603- if (elapsed_time <= CRASH_TIME) {
604- crash_count.set(name, ++crashes);
605- stdout.printf(" [CRASH #%i]", crashes);
606- }
607-
608- if (crashes <= MAX_CRASHES && (if_exited (status) || if_signaled (status) || core_dump (status)))
609- watch_process (name);
610-
611- if (crashes == MAX_CRASHES)
612- stdout.printf("\n\n-- Process '%s' crashed too many times. It won't be launched again.\n\n", name);
613+
614+ // Run
615+ process.run_async ();
616 }
617 }
618-

Subscribers

People subscribed via source and target branches

to all changes: