Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: History file locking and other improvements #7005

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 20 additions & 58 deletions lib/internal/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ const debug = require('util').debuglog('repl');
module.exports = Object.create(REPL);
module.exports.createInternalRepl = createRepl;

// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary.
// The debounce is to guard against code pasted into the REPL.
const kDebounceHistoryMS = 15;

function createRepl(env, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
Expand Down Expand Up @@ -65,8 +61,8 @@ function createRepl(env, opts, cb) {
}

function setupHistory(repl, historyPath, oldHistoryPath, ready) {
let initialHistoryLength = 0;
// Empty string disables persistent history.

if (typeof historyPath === 'string')
historyPath = historyPath.trim();

Expand All @@ -89,9 +85,6 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) {
}
}

var timer = null;
var writing = false;
var pending = false;
repl.pause();
// History files are conventionally not readable by others:
// https://github.com/nodejs/node/issues/3392
Expand Down Expand Up @@ -121,13 +114,18 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) {
fs.readFile(historyPath, 'utf8', onread);
}

function parseData(data) {
return data.toString().trim().split(/[\n\r]+/).slice(0, repl.historySize);
}

function onread(err, data) {
if (err) {
return ready(err);
}

if (data) {
repl.history = data.split(/[\n\r]+/, repl.historySize);
repl.history = parseData(data);
initialHistoryLength = repl.history.length;
} else if (oldHistoryPath === historyPath) {
// If pre-v3.0, the user had set NODE_REPL_HISTORY_FILE to
// ~/.node_repl_history, warn the user about it and proceed.
Expand Down Expand Up @@ -162,62 +160,26 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) {
}
}
}

fs.open(historyPath, 'w', onhandle);
}

function onhandle(err, hnd) {
if (err) {
return ready(err);
}
repl._historyHandle = hnd;
repl.on('line', online);

// reading the file data out erases it
repl.once('flushHistory', function() {
repl.resume();
ready(null, repl);
});
flushHistory();
}

// ------ history listeners ------
function online() {
repl._flushing = true;

if (timer) {
clearTimeout(timer);
}

timer = setTimeout(flushHistory, kDebounceHistoryMS);
// Flush history on close
repl.on('close', flushHistory);
repl.resume();
ready(null, repl);
}

function flushHistory() {
timer = null;
if (writing) {
pending = true;
return;
}
writing = true;
const historyData = repl.history.join(os.EOL);
fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten);
}

function onwritten(err, data) {
writing = false;
if (pending) {
pending = false;
online();
} else {
repl._flushing = Boolean(timer);
if (!repl._flushing) {
repl.emit('flushHistory');
}
const data = fs.readFileSync(historyPath, 'utf8');
const newLines = repl.history.length - initialHistoryLength;
let history = repl.history.slice(0, newLines);
if (data) {
history = history.concat(parseData(data)).slice(0, repl.historySize);
}
const fd = fs.openSync(historyPath, 'a');
fs.ftruncateSync(fd, 0);
fs.writeSync(fd, history.join('\n') + '\n', 0, 'utf8');
fs.closeSync(fd);
}
}


function _replHistoryMessage() {
if (this.history.length === 0) {
this._writeToOutput(
Expand Down
10 changes: 1 addition & 9 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ function REPLServer(prompt,
self.setPrompt(self._prompt);

self.on('close', function() {
self.emit('exit');
process.nextTick(() => self.emit('exit'));
});

var sawSIGINT = false;
Expand Down Expand Up @@ -525,14 +525,6 @@ exports.start = function(prompt,
};

REPLServer.prototype.close = function replClose() {
if (this.terminal && this._flushing && !this._closingOnFlush) {
this._closingOnFlush = true;
this.once('flushHistory', () =>
Interface.prototype.close.call(this)
);

return;
}
process.nextTick(() =>
Interface.prototype.close.call(this)
);
Expand Down
17 changes: 5 additions & 12 deletions test/parallel/test-repl-persistent-history.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ const tests = [
];
const numtests = tests.length;


var testsNotRan = tests.length;

process.on('beforeExit', () =>
Expand All @@ -209,7 +208,8 @@ function cleanupTmpFile() {

// Copy our fixture to the tmp directory
fs.createReadStream(historyFixturePath)
.pipe(fs.createWriteStream(historyPath)).on('unpipe', () => runTest());
.pipe(fs.createWriteStream(historyPath)).on('unpipe',
common.mustCall(runTest));

function runTest(assertCleaned) {
const opts = tests.shift();
Expand Down Expand Up @@ -247,7 +247,7 @@ function runTest(assertCleaned) {
try {
assert.strictEqual(output, expected.shift());
} catch (err) {
console.error(`Failed test # ${numtests - tests.length}`);
console.error(`Failed test # ${numtests - tests.length}. ${err}`);
throw err;
}
next();
Expand All @@ -262,16 +262,9 @@ function runTest(assertCleaned) {
throw err;
}

repl.once('close', () => {
if (repl._flushing) {
repl.once('flushHistory', onClose);
return;
}

onClose();
});
repl.once('exit', common.mustCall(onExit));

function onClose() {
function onExit() {
const cleaned = after ? after() : cleanupTmpFile();

try {
Expand Down