Improve check for DBENGINE max datafile size allowed (#21390)
Enhance datafile size check to prevent overflow during writes
Stelios Fragkakis committed
Dec 3, 2025 at 19:21 UTC
a26cabbb5c49cec326b140c4f52c353f87fb4924
1 file changed
+17
-16
src/database/engine/rrdengine.c
+17
-16
@@ -740,7 +740,7 @@ extent_flush_to_open(struct rrdengine_instance *ctx, struct extent_io_descriptor
740
741
// Main event loop callback
742
743
-static bool datafile_is_full(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile) {
743
+static bool datafile_is_full(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, uint64_t extent_size) {
744
bool ret = false;
745
746
spinlock_lock(&datafile->writers.spinlock);
@@ -754,7 +754,8 @@ static bool datafile_is_full(struct rrdengine_instance *ctx, struct rrdengine_da
754
}
755
#endif
756
757
- if(datafile->pos > rrdeng_target_data_file_size(ctx))
757
+ // Check if adding this extent would exceed the target size
758
+ if(datafile->pos + extent_size > rrdeng_target_data_file_size(ctx))
759
ret = true;
760
761
spinlock_unlock(&datafile->writers.spinlock);
@@ -847,9 +848,14 @@ static void __attribute__((destructor)) destroy_mutex(void) {
848
netdata_mutex_destroy(&mutex);
849
}
850
850
-static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_instance *ctx) {
851
+static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_instance *ctx, uint64_t extent_size) {
852
struct rrdengine_datafile *datafile;
853
854
+ // Acquire the mutex at the beginning to make the entire check-and-act atomic
855
+ // This prevents the race condition where multiple threads pass the "is full" check
856
+ // before any of them increments the position, causing files to grow beyond limits
857
+ netdata_mutex_lock(&mutex);
858
+
859
// get the latest datafile
860
netdata_rwlock_rdlock(&ctx->datafiles.rwlock);
861
@@ -860,23 +866,15 @@ static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_
866
spinlock_unlock(&datafile->writers.spinlock);
867
netdata_rwlock_rdunlock(&ctx->datafiles.rwlock);
868
863
- if(datafile_is_full(ctx, datafile)) {
869
+ if(datafile_is_full(ctx, datafile, extent_size)) {
870
// remember the datafile we have become writers to
871
struct rrdengine_datafile *old_datafile = datafile;
872
867
- // only 1 datafile creation at a time
868
-
869
- netdata_mutex_lock(&mutex);
870
-
871
- // take the latest datafile again - without this, multiple threads may create multiple files
872
- datafile = get_last_ctx_datafile(ctx, false);
873
-
874
- if(datafile_is_full(ctx, datafile) && create_new_datafile_pair(ctx) == 0)
873
+ // Create a new datafile - since we hold the mutex, no other thread can interfere
874
+ if(create_new_datafile_pair(ctx) == 0)
875
__atomic_store_n(&ctx->atomic.needs_indexing, true, __ATOMIC_RELAXED);
876
877
- netdata_mutex_unlock(&mutex);
878
-
879
- // get the new latest datafile again, like above
877
+ // get the new datafile
878
netdata_rwlock_rdlock(&ctx->datafiles.rwlock);
879
datafile = get_last_ctx_datafile(ctx, true);
880
// become a writer on this datafile, to prevent it from vanishing
@@ -897,6 +895,8 @@ static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_
895
spinlock_unlock(&old_datafile->writers.spinlock);
896
}
897
898
+ netdata_mutex_unlock(&mutex);
899
+
900
return datafile;
901
}
902
@@ -1007,7 +1007,8 @@ datafile_extent_build(struct rrdengine_instance *ctx, struct page_descr_with_dat
1007
1008
real_io_size = ALIGN_BYTES_CEILING(size_bytes);
1009
1010
- datafile = get_datafile_to_write_extent(ctx);
1010
+ // Pass the extent size so the check can determine if this extent will fit
1011
+ datafile = get_datafile_to_write_extent(ctx, real_io_size);
1012
spinlock_lock(&datafile->writers.spinlock);
1013
xt_io_descr->datafile = datafile;
1014
xt_io_descr->pos = datafile->pos;