mirror of
https://github.com/amark/gun.git
synced 2026-02-16 08:34:18 +00:00
fix radisk (#1416)
Previously, when a file split occurred, the operations to write the new split file and update (truncate) the existing file were initiated in parallel. This created a race condition: if writing the new file failed but updating the existing file succeeded, the data intended for the new file would be permanently lost. This change strictly sequences the operations. The existing file is now only updated after the new split file has been successfully written and acknowledged, ensuring data integrity even in the event of partial write failures.
This commit is contained in:
@@ -189,12 +189,14 @@
|
||||
f.sub = Radix();
|
||||
Radix.map(rad, f.slice, {reverse: 1}); // IMPORTANT: DO THIS IN REVERSE, SO LAST HALF OF DATA MOVED TO NEW FILE BEFORE DROPPING FROM CURRENT FILE.
|
||||
DBG && (DBG.wf2 = +new Date);
|
||||
r.write(f.end, f.sub, f.both, o);
|
||||
DBG && (DBG.wf3 = +new Date);
|
||||
f.hub = Radix();
|
||||
Radix.map(rad, f.stop);
|
||||
DBG && (DBG.wf4 = +new Date);
|
||||
r.write(rad.file, f.hub, f.both, o);
|
||||
r.write(f.end, f.sub, function(err, ok){
|
||||
if(err){ return cb(err) }
|
||||
DBG && (DBG.wf3 = +new Date);
|
||||
f.hub = Radix();
|
||||
Radix.map(rad, f.stop);
|
||||
DBG && (DBG.wf4 = +new Date);
|
||||
r.write(rad.file, f.hub, cb, o);
|
||||
}, o);
|
||||
DBG && (DBG.wf5 = +new Date);
|
||||
console.STAT && console.STAT(S, +new Date - S, "rad split", ename(rad.file), SC);
|
||||
return true;
|
||||
|
||||
73
test/panic/radisk_split_failure.js
Normal file
73
test/panic/radisk_split_failure.js
Normal file
@@ -0,0 +1,73 @@
|
||||
var Radisk = require('../../lib/radisk.js');
|
||||
var Radix = require('../../lib/radix.js');
|
||||
|
||||
// Mock options
|
||||
var opt = {
|
||||
file: 'radata_test_split_failure',
|
||||
chunk: 10, // Very small chunk to force split
|
||||
store: {
|
||||
get: function (file, cb) {
|
||||
cb(null, null);
|
||||
},
|
||||
put: function (file, data, cb) {
|
||||
cb(null, 'ok');
|
||||
},
|
||||
},
|
||||
log: function () {}, // Silence logs
|
||||
};
|
||||
|
||||
var r = Radisk(opt);
|
||||
|
||||
// Construct a radix tree that needs splitting
|
||||
// Key size + value size + overhead > 10
|
||||
// Entry: len#key:val\n
|
||||
// 3#key:val\n -> ~10 chars
|
||||
var tree = Radix();
|
||||
// Add enough keys to ensure split happens (need > 1 key)
|
||||
for (var i = 10; i < 20; i++) {
|
||||
tree('k' + i, 'val' + i);
|
||||
}
|
||||
// tree file
|
||||
tree.file = 'root_file';
|
||||
|
||||
console.log('--- TEST: Split Failure Safety ---');
|
||||
|
||||
var putCalls = [];
|
||||
opt.store.put = function (file, data, cb) {
|
||||
putCalls.push(file);
|
||||
console.log('Store.put called for:', file);
|
||||
|
||||
if (file === 'root_file') {
|
||||
// This is the old file being overwritten/truncated
|
||||
cb(null, 'ok');
|
||||
} else {
|
||||
// This is the new split file
|
||||
console.log('Simulating FAILURE for new file:', file);
|
||||
cb('MockWriteError');
|
||||
}
|
||||
};
|
||||
|
||||
r.write('root_file', tree, function (err, ok) {
|
||||
console.log('Callback received:', err);
|
||||
|
||||
// Check results
|
||||
var newFileCalls = putCalls.filter(function (f) {
|
||||
return f !== 'root_file';
|
||||
});
|
||||
var oldFileCalls = putCalls.filter(function (f) {
|
||||
return f === 'root_file';
|
||||
});
|
||||
|
||||
if (newFileCalls.length === 0) {
|
||||
console.log('FAILURE: Did not attempt to write new file (Did not split?)');
|
||||
} else if (oldFileCalls.length > 0) {
|
||||
console.log(
|
||||
'FAILURE: Old file was written despite new file failure! DATA LOSS RISK.',
|
||||
);
|
||||
console.log('Writes:', putCalls);
|
||||
} else {
|
||||
console.log(
|
||||
'SUCCESS: Old file was NOT written after new file failure. Data is safe.',
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user