Skip to content

Commit e0209d8

Browse files
committed
core: add NOP jobs, job type collapsing
Two of our current job types are special: JOB_TRY_RESTART, JOB_RELOAD_OR_START. They differ from other job types by being sensitive to the unit active state. They perform some action when the unit is active and some other action otherwise. This raises a question: when exactly should the unit state be checked to make the decision? Currently the unit state is checked when the job becomes runnable. It's more sensible to check the state immediately when the job is added by the user. When the user types "systemctl try-restart foo.service", he really intends to restart the service if it's running right now. If it isn't running right now, the restart is pointless. Consider the example (from Bugzilla[1]): sleep.service takes some time to start. hello.service has After=sleep.service. Both services get started. Two jobs will appear: hello.service/start waiting sleep.service/start running Then someone runs "systemctl try-restart hello.service". Currently the try-restart operation will block and wait for sleep.service/start to complete. The correct result is to complete the try-restart operation immediately with success, because hello.service is not running. The two original jobs must not be disturbed by this. To fix this we introduce two new concepts: - a new job type: JOB_NOP A JOB_NOP job does not do anything to the unit. It does not pull in any dependencies. It is always immediately runnable. When installed to a unit, it sits in a special slot (u->nop_job) where it never conflicts with the installed job (u->job) of a different type. It never merges with jobs of other types, but it can merge into an already installed JOB_NOP job. - "collapsing" of job types When a job of one of the two special types is added, the state of the unit is checked immediately and the job type changes: JOB_TRY_RESTART -> JOB_RESTART or JOB_NOP JOB_RELOAD_OR_START -> JOB_RELOAD or JOB_START Should a job type JOB_RELOAD_OR_START appear later during job merging, it collapses immediately afterwards. Collapsing actually makes some things simpler, because there are now fewer job types that are allowed in the transaction. [1] Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=753586
1 parent e2875c4 commit e0209d8

File tree

8 files changed

+235
-121
lines changed

8 files changed

+235
-121
lines changed

Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,8 @@ test_job_type_CFLAGS = \
904904
$(DBUS_CFLAGS)
905905

906906
test_job_type_LDADD = \
907-
libsystemd-core.la
907+
libsystemd-core.la \
908+
libsystemd-daemon.la
908909

909910
test_ns_SOURCES = \
910911
src/test/test-ns.c

src/core/job.c

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Job* job_new_raw(Unit *unit) {
6060

6161
j->manager = unit->manager;
6262
j->unit = unit;
63+
j->type = _JOB_TYPE_INVALID;
6364
j->timer_watch.type = WATCH_INVALID;
6465

6566
return j;
@@ -115,15 +116,21 @@ void job_free(Job *j) {
115116
}
116117

117118
void job_uninstall(Job *j) {
119+
Job **pj;
120+
118121
assert(j->installed);
119-
assert(j->unit->job == j);
122+
123+
pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
124+
assert(*pj == j);
125+
120126
/* Detach from next 'bigger' objects */
121127

122128
/* daemon-reload should be transparent to job observers */
123129
if (j->manager->n_reloading <= 0)
124130
bus_job_send_removed_signal(j);
125131

126-
j->unit->job = NULL;
132+
*pj = NULL;
133+
127134
unit_add_to_gc_queue(j->unit);
128135

129136
hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id));
@@ -144,31 +151,38 @@ static bool job_type_allows_late_merge(JobType t) {
144151
* patched into JOB_START after stopping the unit. So if we see a
145152
* JOB_RESTART running, it means the unit hasn't stopped yet and at
146153
* this time the merge is still allowed. */
147-
return !(t == JOB_RELOAD || t == JOB_RELOAD_OR_START);
154+
return t != JOB_RELOAD;
148155
}
149156

150157
static void job_merge_into_installed(Job *j, Job *other) {
151158
assert(j->installed);
152159
assert(j->unit == other->unit);
153160

154-
j->type = job_type_lookup_merge(j->type, other->type);
155-
assert(j->type >= 0);
161+
if (j->type != JOB_NOP)
162+
job_type_merge_and_collapse(&j->type, other->type, j->unit);
163+
else
164+
assert(other->type == JOB_NOP);
156165

157166
j->override = j->override || other->override;
158167
}
159168

160169
Job* job_install(Job *j) {
161-
Job *uj = j->unit->job;
170+
Job **pj;
171+
Job *uj;
162172

163173
assert(!j->installed);
174+
assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
175+
176+
pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
177+
uj = *pj;
164178

165179
if (uj) {
166-
if (job_type_is_conflicting(uj->type, j->type))
180+
if (j->type != JOB_NOP && job_type_is_conflicting(uj->type, j->type))
167181
job_finish_and_invalidate(uj, JOB_CANCELED, true);
168182
else {
169183
/* not conflicting, i.e. mergeable */
170184

171-
if (uj->state == JOB_WAITING ||
185+
if (j->type == JOB_NOP || uj->state == JOB_WAITING ||
172186
(job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) {
173187
job_merge_into_installed(uj, j);
174188
log_debug("Merged into installed job %s/%s as %u",
@@ -189,23 +203,33 @@ Job* job_install(Job *j) {
189203
}
190204

191205
/* Install the job */
192-
j->unit->job = j;
206+
*pj = j;
193207
j->installed = true;
194208
j->manager->n_installed_jobs ++;
195209
log_debug("Installed new job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
196210
return j;
197211
}
198212

199-
void job_install_deserialized(Job *j) {
213+
int job_install_deserialized(Job *j) {
214+
Job **pj;
215+
200216
assert(!j->installed);
201217

202-
if (j->unit->job) {
218+
if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) {
219+
log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type)));
220+
return -EINVAL;
221+
}
222+
223+
pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job;
224+
225+
if (*pj) {
203226
log_debug("Unit %s already has a job installed. Not installing deserialized job.", j->unit->id);
204-
return;
227+
return -EEXIST;
205228
}
206-
j->unit->job = j;
229+
*pj = j;
207230
j->installed = true;
208231
log_debug("Reinstalled deserialized job %s/%s as %u", j->unit->id, job_type_to_string(j->type), (unsigned) j->id);
232+
return 0;
209233
}
210234

211235
JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) {
@@ -268,6 +292,10 @@ void job_dump(Job *j, FILE*f, const char *prefix) {
268292
* its lower triangle to avoid duplication. We don't store the main diagonal,
269293
* because A merged with A is simply A.
270294
*
295+
* If the resulting type is collapsed immediately afterwards (to get rid of
296+
* the JOB_RELOAD_OR_START, which lies outside the lookup function's domain),
297+
* the following properties hold:
298+
*
271299
* Merging is associative! A merged with B merged with C is the same as
272300
* A merged with C merged with B.
273301
*
@@ -278,21 +306,19 @@ void job_dump(Job *j, FILE*f, const char *prefix) {
278306
* be merged with C either.
279307
*/
280308
static const JobType job_merging_table[] = {
281-
/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD JOB_RELOAD_OR_START JOB_RESTART JOB_TRY_RESTART */
282-
/************************************************************************************************************************************/
309+
/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */
310+
/*********************************************************************************/
283311
/*JOB_START */
284312
/*JOB_VERIFY_ACTIVE */ JOB_START,
285313
/*JOB_STOP */ -1, -1,
286314
/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1,
287-
/*JOB_RELOAD_OR_START*/ JOB_RELOAD_OR_START, JOB_RELOAD_OR_START, -1, JOB_RELOAD_OR_START,
288-
/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, JOB_RESTART,
289-
/*JOB_TRY_RESTART */ JOB_RESTART, JOB_TRY_RESTART, -1, JOB_TRY_RESTART, JOB_RESTART, JOB_RESTART,
315+
/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART,
290316
};
291317

292318
JobType job_type_lookup_merge(JobType a, JobType b) {
293-
assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX * (_JOB_TYPE_MAX - 1) / 2);
294-
assert(a >= 0 && a < _JOB_TYPE_MAX);
295-
assert(b >= 0 && b < _JOB_TYPE_MAX);
319+
assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2);
320+
assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING);
321+
assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING);
296322

297323
if (a == b)
298324
return a;
@@ -328,24 +354,50 @@ bool job_type_is_redundant(JobType a, UnitActiveState b) {
328354
return
329355
b == UNIT_RELOADING;
330356

331-
case JOB_RELOAD_OR_START:
332-
return
333-
b == UNIT_ACTIVATING ||
334-
b == UNIT_RELOADING;
335-
336357
case JOB_RESTART:
337358
return
338359
b == UNIT_ACTIVATING;
339360

361+
default:
362+
assert_not_reached("Invalid job type");
363+
}
364+
}
365+
366+
void job_type_collapse(JobType *t, Unit *u) {
367+
UnitActiveState s;
368+
369+
switch (*t) {
370+
340371
case JOB_TRY_RESTART:
341-
return
342-
b == UNIT_ACTIVATING;
372+
s = unit_active_state(u);
373+
if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s))
374+
*t = JOB_NOP;
375+
else
376+
*t = JOB_RESTART;
377+
break;
378+
379+
case JOB_RELOAD_OR_START:
380+
s = unit_active_state(u);
381+
if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s))
382+
*t = JOB_START;
383+
else
384+
*t = JOB_RELOAD;
385+
break;
343386

344387
default:
345-
assert_not_reached("Invalid job type");
388+
;
346389
}
347390
}
348391

392+
int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) {
393+
JobType t = job_type_lookup_merge(*a, b);
394+
if (t < 0)
395+
return -EEXIST;
396+
*a = t;
397+
job_type_collapse(a, u);
398+
return 0;
399+
}
400+
349401
bool job_is_runnable(Job *j) {
350402
Iterator i;
351403
Unit *other;
@@ -362,10 +414,12 @@ bool job_is_runnable(Job *j) {
362414
if (j->ignore_order)
363415
return true;
364416

417+
if (j->type == JOB_NOP)
418+
return true;
419+
365420
if (j->type == JOB_START ||
366421
j->type == JOB_VERIFY_ACTIVE ||
367-
j->type == JOB_RELOAD ||
368-
j->type == JOB_RELOAD_OR_START) {
422+
j->type == JOB_RELOAD) {
369423

370424
/* Immediate result is that the job is or might be
371425
* started. In this case lets wait for the
@@ -383,8 +437,7 @@ bool job_is_runnable(Job *j) {
383437
SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i)
384438
if (other->job &&
385439
(other->job->type == JOB_STOP ||
386-
other->job->type == JOB_RESTART ||
387-
other->job->type == JOB_TRY_RESTART))
440+
other->job->type == JOB_RESTART))
388441
return false;
389442

390443
/* This means that for a service a and a service b where b
@@ -416,6 +469,7 @@ int job_run_and_invalidate(Job *j) {
416469

417470
assert(j);
418471
assert(j->installed);
472+
assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
419473

420474
if (j->in_run_queue) {
421475
LIST_REMOVE(Job, run_queue, j->manager->run_queue, j);
@@ -441,15 +495,6 @@ int job_run_and_invalidate(Job *j) {
441495

442496
switch (j->type) {
443497

444-
case JOB_RELOAD_OR_START:
445-
if (unit_active_state(j->unit) == UNIT_ACTIVE) {
446-
job_change_type(j, JOB_RELOAD);
447-
r = unit_reload(j->unit);
448-
break;
449-
}
450-
job_change_type(j, JOB_START);
451-
/* fall through */
452-
453498
case JOB_START:
454499
r = unit_start(j->unit);
455500

@@ -469,14 +514,6 @@ int job_run_and_invalidate(Job *j) {
469514
break;
470515
}
471516

472-
case JOB_TRY_RESTART:
473-
if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(j->unit))) {
474-
r = -ENOEXEC;
475-
break;
476-
}
477-
job_change_type(j, JOB_RESTART);
478-
/* fall through */
479-
480517
case JOB_STOP:
481518
case JOB_RESTART:
482519
r = unit_stop(j->unit);
@@ -490,11 +527,16 @@ int job_run_and_invalidate(Job *j) {
490527
r = unit_reload(j->unit);
491528
break;
492529

530+
case JOB_NOP:
531+
r = -EALREADY;
532+
break;
533+
493534
default:
494535
assert_not_reached("Unknown job type");
495536
}
496537

497-
if ((j = manager_get_job(m, id))) {
538+
j = manager_get_job(m, id);
539+
if (j) {
498540
if (r == -EALREADY)
499541
r = job_finish_and_invalidate(j, JOB_DONE, true);
500542
else if (r == -ENOEXEC)
@@ -564,6 +606,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) {
564606

565607
assert(j);
566608
assert(j->installed);
609+
assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION);
567610

568611
job_add_to_dbus_queue(j);
569612

@@ -597,38 +640,33 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) {
597640
if (result != JOB_DONE && recursive) {
598641

599642
if (t == JOB_START ||
600-
t == JOB_VERIFY_ACTIVE ||
601-
t == JOB_RELOAD_OR_START) {
643+
t == JOB_VERIFY_ACTIVE) {
602644

603645
SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY], i)
604646
if (other->job &&
605647
(other->job->type == JOB_START ||
606-
other->job->type == JOB_VERIFY_ACTIVE ||
607-
other->job->type == JOB_RELOAD_OR_START))
648+
other->job->type == JOB_VERIFY_ACTIVE))
608649
job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
609650

610651
SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i)
611652
if (other->job &&
612653
(other->job->type == JOB_START ||
613-
other->job->type == JOB_VERIFY_ACTIVE ||
614-
other->job->type == JOB_RELOAD_OR_START))
654+
other->job->type == JOB_VERIFY_ACTIVE))
615655
job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
616656

617657
SET_FOREACH(other, u->dependencies[UNIT_REQUIRED_BY_OVERRIDABLE], i)
618658
if (other->job &&
619659
!other->job->override &&
620660
(other->job->type == JOB_START ||
621-
other->job->type == JOB_VERIFY_ACTIVE ||
622-
other->job->type == JOB_RELOAD_OR_START))
661+
other->job->type == JOB_VERIFY_ACTIVE))
623662
job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
624663

625664
} else if (t == JOB_STOP) {
626665

627666
SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i)
628667
if (other->job &&
629668
(other->job->type == JOB_START ||
630-
other->job->type == JOB_VERIFY_ACTIVE ||
631-
other->job->type == JOB_RELOAD_OR_START))
669+
other->job->type == JOB_VERIFY_ACTIVE))
632670
job_finish_and_invalidate(other->job, JOB_DEPENDENCY, true);
633671
}
634672
}
@@ -808,6 +846,8 @@ int job_deserialize(Job *j, FILE *f, FDSet *fds) {
808846
JobType t = job_type_from_string(v);
809847
if (t < 0)
810848
log_debug("Failed to parse job type %s", v);
849+
else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION)
850+
log_debug("Cannot deserialize job of type %s", v);
811851
else
812852
j->type = t;
813853
} else if (streq(l, "job-state")) {
@@ -887,6 +927,7 @@ static const char* const job_type_table[_JOB_TYPE_MAX] = {
887927
[JOB_RELOAD_OR_START] = "reload-or-start",
888928
[JOB_RESTART] = "restart",
889929
[JOB_TRY_RESTART] = "try-restart",
930+
[JOB_NOP] = "nop",
890931
};
891932

892933
DEFINE_STRING_TABLE_LOOKUP(job_type, JobType);

0 commit comments

Comments
 (0)