Skip to content

Commit

Permalink
Rework how we cache mtime to figure out if units changed
Browse files Browse the repository at this point in the history
Instead of assuming that more-recently modified directories have higher mtime,
just look for any mtime changes, up or down. Since we don't want to remember
individual mtimes, hash them to obtain a single value.

This should help us behave properly in the case when the time jumps backwards
during boot: various files might have mtimes that in the future, but we won't
care. This fixes the following scenario:

We have /etc/systemd/system with T1. T1 is initially far in the past.
We have /run/systemd/generator with time T2.
The time is adjusted backwards, so T2 will be always in the future for a while.
Now the user writes new files to /etc/systemd/system, and T1 is updated to T1'.
Nevertheless, T1 < T1' << T2.
We would consider our cache to be up-to-date, falsely.

(cherry picked from commit c2911d4)
  • Loading branch information
keszybz committed Sep 1, 2020
1 parent 0500968 commit 97fdde3
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 36 deletions.
6 changes: 6 additions & 0 deletions src/basic/siphash24.h
Expand Up @@ -6,6 +6,8 @@
#include <string.h>
#include <sys/types.h>

#include "time-util.h"

struct siphash {
uint64_t v0;
uint64_t v1;
Expand All @@ -25,6 +27,10 @@ static inline void siphash24_compress_boolean(bool in, struct siphash *state) {
siphash24_compress(&i, sizeof i, state);
}

static inline void siphash24_compress_usec_t(usec_t in, struct siphash *state) {
siphash24_compress(&in, sizeof in, state);
}

static inline void siphash24_compress_string(const char *in, struct siphash *state) {
if (!in)
return;
Expand Down
2 changes: 1 addition & 1 deletion src/core/load-fragment.c
Expand Up @@ -4943,7 +4943,7 @@ int unit_load_fragment(Unit *u) {

/* Possibly rebuild the fragment map to catch new units */
r = unit_file_build_name_map(&u->manager->lookup_paths,
&u->manager->unit_cache_mtime,
&u->manager->unit_cache_timestamp_hash,
&u->manager->unit_id_map,
&u->manager->unit_name_map,
&u->manager->unit_path_cache);
Expand Down
6 changes: 3 additions & 3 deletions src/core/manager.c
Expand Up @@ -703,7 +703,7 @@ static void manager_free_unit_name_maps(Manager *m) {
m->unit_id_map = hashmap_free(m->unit_id_map);
m->unit_name_map = hashmap_free(m->unit_name_map);
m->unit_path_cache = set_free(m->unit_path_cache);
m->unit_cache_mtime = 0;
m->unit_cache_timestamp_hash = 0;
}

static int manager_setup_run_queue(Manager *m) {
Expand Down Expand Up @@ -1948,11 +1948,11 @@ bool manager_unit_cache_should_retry_load(Unit *u) {

/* The cache has been updated since the last time we tried to load the unit. There might be new
* fragment paths to read. */
if (u->manager->unit_cache_mtime != u->fragment_not_found_time)
if (u->manager->unit_cache_timestamp_hash != u->fragment_not_found_timestamp_hash)
return true;

/* The cache needs to be updated because there are modifications on disk. */
return !lookup_paths_mtime_good(&u->manager->lookup_paths, u->manager->unit_cache_mtime);
return !lookup_paths_timestamp_hash_same(&u->manager->lookup_paths, u->manager->unit_cache_timestamp_hash, NULL);
}

int manager_load_unit_prepare(
Expand Down
2 changes: 1 addition & 1 deletion src/core/manager.h
Expand Up @@ -233,7 +233,7 @@ struct Manager {
Hashmap *unit_id_map;
Hashmap *unit_name_map;
Set *unit_path_cache;
usec_t unit_cache_mtime;
uint64_t unit_cache_timestamp_hash;

char **transient_environment; /* The environment, as determined from config files, kernel cmdline and environment generators */
char **client_environment; /* Environment variables created by clients through the bus API */
Expand Down
2 changes: 1 addition & 1 deletion src/core/unit.c
Expand Up @@ -1685,7 +1685,7 @@ int unit_load(Unit *u) {
/* Record the timestamp on the cache, so that if the cache gets updated between now and the next time
* an attempt is made to load this unit, we know we need to check again. */
if (u->load_state == UNIT_NOT_FOUND)
u->fragment_not_found_time = u->manager->unit_cache_mtime;
u->fragment_not_found_timestamp_hash = u->manager->unit_cache_timestamp_hash;

unit_add_to_dbus_queue(u);
unit_add_to_gc_queue(u);
Expand Down
2 changes: 1 addition & 1 deletion src/core/unit.h
Expand Up @@ -136,7 +136,7 @@ typedef struct Unit {
char *source_path; /* if converted, the source file */
char **dropin_paths;

usec_t fragment_not_found_time;
usec_t fragment_not_found_timestamp_hash;
usec_t fragment_mtime;
usec_t source_mtime;
usec_t dropin_mtime;
Expand Down
51 changes: 28 additions & 23 deletions src/shared/unit-file.c
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */

#include "sd-id128.h"

#include "dirent-util.h"
#include "fd-util.h"
#include "fs-util.h"
Expand Down Expand Up @@ -199,9 +201,14 @@ static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path)
streq_ptr(path, lp->runtime_control);
}

bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime) {
char **dir;
#define HASH_KEY SD_ID128_MAKE(4e,86,1b,e3,39,b3,40,46,98,5d,b8,11,34,8f,c3,c1)

bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new) {
struct siphash state;

siphash24_init(&state, HASH_KEY.bytes);

char **dir;
STRV_FOREACH(dir, (char**) lp->search_path) {
struct stat st;

Expand All @@ -217,18 +224,20 @@ bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime) {
continue;
}

if (timespec_load(&st.st_mtim) > mtime) {
log_debug_errno(errno, "Unit dir %s has changed, need to update cache.", *dir);
return false;
}
siphash24_compress_usec_t(timespec_load(&st.st_mtim), &state);
}

return true;
uint64_t updated = siphash24_finalize(&state);
if (ret_new)
*ret_new = updated;
if (updated != timestamp_hash)
log_debug("Modification times have changed, need to update cache.");
return updated == timestamp_hash;
}

int unit_file_build_name_map(
const LookupPaths *lp,
usec_t *cache_mtime,
uint64_t *cache_timestamp_hash,
Hashmap **unit_ids_map,
Hashmap **unit_names_map,
Set **path_cache) {
Expand All @@ -245,14 +254,18 @@ int unit_file_build_name_map(

_cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
_cleanup_set_free_free_ Set *paths = NULL;
uint64_t timestamp_hash;
char **dir;
int r;
usec_t mtime = 0;

/* Before doing anything, check if the mtime that was passed is still valid. If
* yes, do nothing. If *cache_time == 0, always build the cache. */
if (cache_mtime && *cache_mtime > 0 && lookup_paths_mtime_good(lp, *cache_mtime))
return 0;
/* Before doing anything, check if the timestamp hash that was passed is still valid.
* If yes, do nothing. */
if (cache_timestamp_hash &&
lookup_paths_timestamp_hash_same(lp, *cache_timestamp_hash, &timestamp_hash))
return 0;

/* The timestamp hash is now set based on the mtimes from before when we start reading files.
* If anything is modified concurrently, we'll consider the cache outdated. */

if (path_cache) {
paths = set_new(&path_hash_ops_free);
Expand All @@ -263,7 +276,6 @@ int unit_file_build_name_map(
STRV_FOREACH(dir, (char**) lp->search_path) {
struct dirent *de;
_cleanup_closedir_ DIR *d = NULL;
struct stat st;

d = opendir(*dir);
if (!d) {
Expand All @@ -272,13 +284,6 @@ int unit_file_build_name_map(
continue;
}

/* Determine the latest lookup path modification time */
if (fstat(dirfd(d), &st) < 0)
return log_error_errno(errno, "Failed to fstat %s: %m", *dir);

if (!lookup_paths_mtime_exclude(lp, *dir))
mtime = MAX(mtime, timespec_load(&st.st_mtim));

FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
char *filename;
_cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
Expand Down Expand Up @@ -417,8 +422,8 @@ int unit_file_build_name_map(
basename(dst), src);
}

if (cache_mtime)
*cache_mtime = mtime;
if (cache_timestamp_hash)
*cache_timestamp_hash = timestamp_hash;

hashmap_free_and_replace(*unit_ids_map, ids);
hashmap_free_and_replace(*unit_names_map, names);
Expand Down
12 changes: 6 additions & 6 deletions src/shared/unit-file.h
Expand Up @@ -43,19 +43,19 @@ bool unit_type_may_template(UnitType type) _const_;
int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation);
int unit_validate_alias_symlink_and_warn(const char *filename, const char *target);

bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime);
bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new);
int unit_file_build_name_map(
const LookupPaths *lp,
usec_t *ret_time,
Hashmap **ret_unit_ids_map,
Hashmap **ret_unit_names_map,
Set **ret_path_cache);
uint64_t *cache_timestamp_hash,
Hashmap **unit_ids_map,
Hashmap **unit_names_map,
Set **path_cache);

int unit_file_find_fragment(
Hashmap *unit_ids_map,
Hashmap *unit_name_map,
const char *unit_name,
const char **ret_fragment_path,
Set **names);
Set **ret_names);

const char* runlevel_to_target(const char *rl);

0 comments on commit 97fdde3

Please sign in to comment.