@cryptotaxi247 / netdata / commits / f568812d4

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(