fix(diskspace): initialize mountpoint state before slow worker (#22298)
* fix(diskspace): initialize mountpoint state before slow worker Move diskspace mountpoint dictionary and pattern initialization out of the collection fast path and run it before starting the slow worker thread. The slow worker can run cleanup independently, so publishing the shared mountpoint dictionary lazily allowed it to observe partially initialized state and crash while reading mountpoint metadata. * fix(diskspace): initialize shared state before function endpoint publishing
Stelios Fragkakis committed
Apr 28, 2026 at 16:57 UTC
f568812d414e7918c55cb04b9f8092d0514db281
1 file changed
+65
-44
src/collectors/diskspace.plugin/plugin_diskspace.c
+65
-44
@@ -46,6 +46,10 @@ static DICTIONARY *zfs_cache = NULL;
46
static SIMPLE_PATTERN *excluded_zfs_datasets_pattern = NULL;
47
static int zfs_datasets_heuristic = CONFIG_BOOLEAN_YES;
48
49
+static SIMPLE_PATTERN *excluded_mountpoints = NULL;
50
+static SIMPLE_PATTERN *excluded_filesystems = NULL;
51
+static SIMPLE_PATTERN *excluded_filesystems_inodes = NULL;
52
+
53
static inline void mountinfo_reload(int force) {
54
static time_t last_loaded = 0;
55
time_t now = now_realtime_sec();
@@ -93,6 +97,49 @@ static DICTIONARY *dict_mountpoints = NULL;
97
98
#define rrdset_obsolete_and_pointer_null(st) do { if(st) { rrdset_is_obsolete___safe_from_collector_thread(st); (st) = NULL; } } while(st)
99
100
+static void mountpoint_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data __maybe_unused);
101
+
102
+static void diskspace_mountpoints_init(void) {
103
+ if(dict_mountpoints)
104
+ return;
105
+
106
+ SIMPLE_PREFIX_MODE mode = SIMPLE_PATTERN_EXACT;
107
+
108
+ if(inicfg_move(&netdata_config, "plugin:proc:/proc/diskstats", "exclude space metrics on paths", CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths") != -1) {
109
+ // old configuration, enable backwards compatibility
110
+ mode = SIMPLE_PATTERN_PREFIX;
111
+ }
112
+
113
+ excluded_mountpoints = simple_pattern_create(
114
+ inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS),
115
+ NULL,
116
+ mode,
117
+ true);
118
+
119
+ excluded_filesystems = simple_pattern_create(
120
+ inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS),
121
+ NULL,
122
+ SIMPLE_PATTERN_EXACT,
123
+ true);
124
+
125
+ excluded_filesystems_inodes = simple_pattern_create(
126
+ inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude inode metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS_INODES),
127
+ NULL,
128
+ SIMPLE_PATTERN_EXACT,
129
+ true);
130
+
131
+ // ZFS dataset exclusion pattern (used when heuristic is disabled)
132
+ // Always create so the option appears in config for users to customize.
133
+ excluded_zfs_datasets_pattern = simple_pattern_create(
134
+ inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude zfs datasets on paths", "!*"),
135
+ NULL,
136
+ SIMPLE_PATTERN_EXACT,
137
+ true);
138
+
139
+ dict_mountpoints = dictionary_create_advanced(DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(struct mount_point_metadata));
140
+ dictionary_register_delete_callback(dict_mountpoints, mountpoint_delete_cb, NULL);
141
+}
142
+
143
static void mount_points_cleanup(bool slow) {
144
struct mount_point_metadata *mp;
145
dfe_start_write(dict_mountpoints, mp) {
@@ -108,7 +155,7 @@ static void mount_points_cleanup(bool slow) {
155
dictionary_garbage_collect(dict_mountpoints);
156
}
157
111
-void mountpoint_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data __maybe_unused) {
158
+static void mountpoint_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data __maybe_unused) {
159
struct mount_point_metadata *mp = (struct mount_point_metadata *)entry;
160
161
mp->collected = 0;
@@ -504,51 +551,12 @@ static bool should_exclude_zfs(const char *filesystem, const char *mount_point,
551
static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
552
const char *disk = mi->persistent_id;
553
507
- static SIMPLE_PATTERN *excluded_mountpoints = NULL;
508
- static SIMPLE_PATTERN *excluded_filesystems = NULL;
509
- static SIMPLE_PATTERN *excluded_filesystems_inodes = NULL;
510
-
554
usec_t slow_timeout = MAX_STAT_USEC * update_every;
555
556
int do_space, do_inodes;
557
515
- if(unlikely(!dict_mountpoints)) {
516
- SIMPLE_PREFIX_MODE mode = SIMPLE_PATTERN_EXACT;
517
-
518
- if(inicfg_move(&netdata_config, "plugin:proc:/proc/diskstats", "exclude space metrics on paths", CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths") != -1) {
519
- // old configuration, enable backwards compatibility
520
- mode = SIMPLE_PATTERN_PREFIX;
521
- }
522
-
523
- excluded_mountpoints = simple_pattern_create(
524
- inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS),
525
- NULL,
526
- mode,
527
- true);
528
-
529
- excluded_filesystems = simple_pattern_create(
530
- inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS),
531
- NULL,
532
- SIMPLE_PATTERN_EXACT,
533
- true);
534
-
535
- excluded_filesystems_inodes = simple_pattern_create(
536
- inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude inode metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS_INODES),
537
- NULL,
538
- SIMPLE_PATTERN_EXACT,
539
- true);
540
-
541
- // ZFS dataset exclusion pattern (used when heuristic is disabled)
542
- // Always create so the option appears in config for users to customize
543
- excluded_zfs_datasets_pattern = simple_pattern_create(
544
- inicfg_get(&netdata_config, CONFIG_SECTION_DISKSPACE, "exclude zfs datasets on paths", "!*"),
545
- NULL,
546
- SIMPLE_PATTERN_EXACT,
547
- true);
548
-
549
- dict_mountpoints = dictionary_create_advanced(DICT_OPTION_FIXED_SIZE, &dictionary_stats_category_collectors, sizeof(struct mount_point_metadata));
550
- dictionary_register_delete_callback(dict_mountpoints, mountpoint_delete_cb, NULL);
551
- }
558
+ if(unlikely(!dict_mountpoints))
559
+ diskspace_mountpoints_init();
560
561
const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dict_mountpoints, mi->mount_point);
562
if(unlikely(!item)) {
@@ -844,6 +852,15 @@ static void diskspace_main_cleanup(void *ptr) {
852
dictionary_destroy(dict_mountpoints);
853
dict_mountpoints = NULL;
854
855
+ simple_pattern_free(excluded_mountpoints);
856
+ excluded_mountpoints = NULL;
857
+
858
+ simple_pattern_free(excluded_filesystems);
859
+ excluded_filesystems = NULL;
860
+
861
+ simple_pattern_free(excluded_filesystems_inodes);
862
+ excluded_filesystems_inodes = NULL;
863
+
864
// Free ZFS deduplication resources
865
dictionary_destroy(zfs_cache);
866
zfs_cache = NULL;
@@ -1082,6 +1099,12 @@ void diskspace_main(void *ptr) {
1099
worker_register_job_name(WORKER_JOB_MOUNTPOINT, "mountpoint");
1100
worker_register_job_name(WORKER_JOB_CLEANUP, "cleanup");
1101
1102
+ // Initialize shared state before publishing the function endpoint, so that
1103
+ // diskspace_function_mount_points cannot fire against an uninitialized
1104
+ // mutex or a NULL dict_mountpoints.
1105
+ netdata_mutex_init(&slow_mountinfo_mutex);
1106
+ diskspace_mountpoints_init();
1107
+
1108
rrd_function_add_inline(localhost, NULL, "mount-points", 10,
1109
RRDFUNCTIONS_PRIORITY_DEFAULT, RRDFUNCTIONS_VERSION_DEFAULT,
1110
RRDFUNCTIONS_DISKSPACE_HELP,
@@ -1115,8 +1138,6 @@ void diskspace_main(void *ptr) {
1138
sizeof(struct zfs_cache_entry));
1139
}
1140
1118
- netdata_mutex_init(&slow_mountinfo_mutex);
1119
-
1141
struct slow_worker_data slow_worker_data = { .update_every = update_every };
1142
1143
diskspace_slow_thread = nd_thread_create(