mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #8304 "default to 'nofsync'"
This commit is contained in:
commit
ad60927d09
@ -2690,17 +2690,19 @@ A jump table for the options with a short description can be found at |Q_op|.
|
|||||||
security reasons.
|
security reasons.
|
||||||
|
|
||||||
*'fsync'* *'fs'*
|
*'fsync'* *'fs'*
|
||||||
'fsync' 'fs' boolean (default on)
|
'fsync' 'fs' boolean (default off)
|
||||||
global
|
global
|
||||||
When on, the library function fsync() will be called after writing a
|
When on, the OS function fsync() will be called after saving a file
|
||||||
file. This will flush a file to disk, ensuring that it is safely
|
(|:write|, |writefile()|, …), |swap-file| and |shada-file|. This
|
||||||
written even on filesystems which do metadata-only journaling. This
|
flushes the file to disk, ensuring that it is safely written.
|
||||||
will force the harddrive to spin up on Linux systems running in laptop
|
Slow on some systems: writing buffers, quitting Nvim, and other
|
||||||
mode, so it may be undesirable in some situations. Be warned that
|
operations may sometimes take a few seconds.
|
||||||
turning this off increases the chances of data loss after a crash.
|
|
||||||
|
|
||||||
Currently applies only to writing the buffer with e.g. |:w| and
|
Files are ALWAYS flushed ('fsync' is ignored) when:
|
||||||
|writefile()|.
|
- |CursorHold| event is triggered
|
||||||
|
- |:preserve| is called
|
||||||
|
- system signals low battery life
|
||||||
|
- Nvim exits abnormally
|
||||||
|
|
||||||
*'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
|
*'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
|
||||||
'gdefault' 'gd' boolean (default off)
|
'gdefault' 'gd' boolean (default off)
|
||||||
|
@ -1473,6 +1473,17 @@ Float nvim__id_float(Float flt)
|
|||||||
return flt;
|
return flt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets internal stats.
|
||||||
|
///
|
||||||
|
/// @return Map of various internal stats.
|
||||||
|
Dictionary nvim__stats(void)
|
||||||
|
{
|
||||||
|
Dictionary rv = ARRAY_DICT_INIT;
|
||||||
|
PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
|
||||||
|
PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a list of dictionaries representing attached UIs.
|
/// Gets a list of dictionaries representing attached UIs.
|
||||||
///
|
///
|
||||||
/// @return Array of UI dictionaries
|
/// @return Array of UI dictionaries
|
||||||
|
@ -265,7 +265,7 @@ static void process_close_event(void **argv)
|
|||||||
if (proc->type == kProcessTypePty) {
|
if (proc->type == kProcessTypePty) {
|
||||||
xfree(((PtyProcess *)proc)->term_name);
|
xfree(((PtyProcess *)proc)->term_name);
|
||||||
}
|
}
|
||||||
if (proc->cb) {
|
if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start().
|
||||||
proc->cb(proc, proc->status, proc->data);
|
proc->cb(proc, proc->status, proc->data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6552,18 +6552,14 @@ void alist_slash_adjust(void)
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/// ":preserve".
|
||||||
* ":preserve".
|
|
||||||
*/
|
|
||||||
static void ex_preserve(exarg_T *eap)
|
static void ex_preserve(exarg_T *eap)
|
||||||
{
|
{
|
||||||
curbuf->b_flags |= BF_PRESERVED;
|
curbuf->b_flags |= BF_PRESERVED;
|
||||||
ml_preserve(curbuf, TRUE);
|
ml_preserve(curbuf, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// ":recover".
|
||||||
* ":recover".
|
|
||||||
*/
|
|
||||||
static void ex_recover(exarg_T *eap)
|
static void ex_recover(exarg_T *eap)
|
||||||
{
|
{
|
||||||
/* Set recoverymode right away to avoid the ATTENTION prompt. */
|
/* Set recoverymode right away to avoid the ATTENTION prompt. */
|
||||||
|
@ -3068,7 +3068,7 @@ nobackup:
|
|||||||
*/
|
*/
|
||||||
if (reset_changed && !newfile && overwriting
|
if (reset_changed && !newfile && overwriting
|
||||||
&& !(exiting && backup != NULL)) {
|
&& !(exiting && backup != NULL)) {
|
||||||
ml_preserve(buf, FALSE);
|
ml_preserve(buf, false, !!p_fs);
|
||||||
if (got_int) {
|
if (got_int) {
|
||||||
SET_ERRMSG(_(e_interr));
|
SET_ERRMSG(_(e_interr));
|
||||||
goto restore_backup;
|
goto restore_backup;
|
||||||
|
@ -1328,10 +1328,8 @@ int using_script(void)
|
|||||||
return scriptin[curscript] != NULL;
|
return scriptin[curscript] != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// This function is called just before doing a blocking wait. Thus after
|
||||||
* This function is called just before doing a blocking wait. Thus after
|
/// waiting 'updatetime' for a character to arrive.
|
||||||
* waiting 'updatetime' for a character to arrive.
|
|
||||||
*/
|
|
||||||
void before_blocking(void)
|
void before_blocking(void)
|
||||||
{
|
{
|
||||||
updatescript(0);
|
updatescript(0);
|
||||||
@ -1340,21 +1338,22 @@ void before_blocking(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// updatescript() is called when a character can be written to the script file
|
||||||
* updatescipt() is called when a character can be written into the script file
|
/// or when we have waited some time for a character (c == 0).
|
||||||
* or when we have waited some time for a character (c == 0)
|
///
|
||||||
*
|
/// All the changed memfiles are synced if c == 0 or when the number of typed
|
||||||
* All the changed memfiles are synced if c == 0 or when the number of typed
|
/// characters reaches 'updatecount' and 'updatecount' is non-zero.
|
||||||
* characters reaches 'updatecount' and 'updatecount' is non-zero.
|
static void updatescript(int c)
|
||||||
*/
|
|
||||||
void updatescript(int c)
|
|
||||||
{
|
{
|
||||||
static int count = 0;
|
static int count = 0;
|
||||||
|
|
||||||
if (c && scriptout)
|
if (c && scriptout) {
|
||||||
putc(c, scriptout);
|
putc(c, scriptout);
|
||||||
if (c == 0 || (p_uc > 0 && ++count >= p_uc)) {
|
}
|
||||||
ml_sync_all(c == 0, TRUE);
|
bool idle = (c == 0);
|
||||||
|
if (idle || (p_uc > 0 && ++count >= p_uc)) {
|
||||||
|
ml_sync_all(idle, true,
|
||||||
|
(!!p_fs || idle)); // Always fsync at idle (CursorHold).
|
||||||
count = 0;
|
count = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,11 @@ typedef enum {
|
|||||||
kTrue = 1,
|
kTrue = 1,
|
||||||
} TriState;
|
} TriState;
|
||||||
|
|
||||||
|
EXTERN struct nvim_stats_s {
|
||||||
|
int64_t fsync;
|
||||||
|
int64_t redraw;
|
||||||
|
} g_stats INIT(= { 0, 0 });
|
||||||
|
|
||||||
/* Values for "starting" */
|
/* Values for "starting" */
|
||||||
#define NO_SCREEN 2 /* no screen updating yet */
|
#define NO_SCREEN 2 /* no screen updating yet */
|
||||||
#define NO_BUFFERS 1 /* not all buffers loaded yet */
|
#define NO_BUFFERS 1 /* not all buffers loaded yet */
|
||||||
|
@ -1593,7 +1593,7 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
|
|||||||
* If 'check_char' is TRUE, stop syncing when character becomes available, but
|
* If 'check_char' is TRUE, stop syncing when character becomes available, but
|
||||||
* always sync at least one block.
|
* always sync at least one block.
|
||||||
*/
|
*/
|
||||||
void ml_sync_all(int check_file, int check_char)
|
void ml_sync_all(int check_file, int check_char, bool do_fsync)
|
||||||
{
|
{
|
||||||
FOR_ALL_BUFFERS(buf) {
|
FOR_ALL_BUFFERS(buf) {
|
||||||
if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL)
|
if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL)
|
||||||
@ -1612,16 +1612,17 @@ void ml_sync_all(int check_file, int check_char)
|
|||||||
if (!os_fileinfo((char *)buf->b_ffname, &file_info)
|
if (!os_fileinfo((char *)buf->b_ffname, &file_info)
|
||||||
|| file_info.stat.st_mtim.tv_sec != buf->b_mtime_read
|
|| file_info.stat.st_mtim.tv_sec != buf->b_mtime_read
|
||||||
|| os_fileinfo_size(&file_info) != buf->b_orig_size) {
|
|| os_fileinfo_size(&file_info) != buf->b_orig_size) {
|
||||||
ml_preserve(buf, FALSE);
|
ml_preserve(buf, false, do_fsync);
|
||||||
did_check_timestamps = FALSE;
|
did_check_timestamps = false;
|
||||||
need_check_timestamps = TRUE; /* give message later */
|
need_check_timestamps = true; // give message later
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (buf->b_ml.ml_mfp->mf_dirty) {
|
if (buf->b_ml.ml_mfp->mf_dirty) {
|
||||||
(void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0)
|
(void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0)
|
||||||
| (bufIsChanged(buf) ? MFS_FLUSH : 0));
|
| (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0));
|
||||||
if (check_char && os_char_avail()) /* character available now */
|
if (check_char && os_char_avail()) { // character available now
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1636,7 +1637,7 @@ void ml_sync_all(int check_file, int check_char)
|
|||||||
*
|
*
|
||||||
* when message is TRUE the success of preserving is reported
|
* when message is TRUE the success of preserving is reported
|
||||||
*/
|
*/
|
||||||
void ml_preserve(buf_T *buf, int message)
|
void ml_preserve(buf_T *buf, int message, bool do_fsync)
|
||||||
{
|
{
|
||||||
bhdr_T *hp;
|
bhdr_T *hp;
|
||||||
linenr_T lnum;
|
linenr_T lnum;
|
||||||
@ -1654,9 +1655,9 @@ void ml_preserve(buf_T *buf, int message)
|
|||||||
* before. */
|
* before. */
|
||||||
got_int = FALSE;
|
got_int = FALSE;
|
||||||
|
|
||||||
ml_flush_line(buf); /* flush buffered line */
|
ml_flush_line(buf); // flush buffered line
|
||||||
(void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */
|
(void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
|
||||||
status = mf_sync(mfp, MFS_ALL | MFS_FLUSH);
|
status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0));
|
||||||
|
|
||||||
/* stack is invalid after mf_sync(.., MFS_ALL) */
|
/* stack is invalid after mf_sync(.., MFS_ALL) */
|
||||||
buf->b_ml.ml_stack_top = 0;
|
buf->b_ml.ml_stack_top = 0;
|
||||||
@ -1684,11 +1685,12 @@ void ml_preserve(buf_T *buf, int message)
|
|||||||
CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum");
|
CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum");
|
||||||
lnum = buf->b_ml.ml_locked_high + 1;
|
lnum = buf->b_ml.ml_locked_high + 1;
|
||||||
}
|
}
|
||||||
(void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */
|
(void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
|
||||||
/* sync the updated pointer blocks */
|
// sync the updated pointer blocks
|
||||||
if (mf_sync(mfp, MFS_ALL | MFS_FLUSH) == FAIL)
|
if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) {
|
||||||
status = FAIL;
|
status = FAIL;
|
||||||
buf->b_ml.ml_stack_top = 0; /* stack is invalid now */
|
}
|
||||||
|
buf->b_ml.ml_stack_top = 0; // stack is invalid now
|
||||||
}
|
}
|
||||||
theend:
|
theend:
|
||||||
got_int |= got_int_save;
|
got_int |= got_int_save;
|
||||||
|
@ -2643,7 +2643,7 @@ void preserve_exit(void)
|
|||||||
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
|
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
|
||||||
mch_errmsg((uint8_t *)"Vim: preserving files...\n");
|
mch_errmsg((uint8_t *)"Vim: preserving files...\n");
|
||||||
ui_flush();
|
ui_flush();
|
||||||
ml_sync_all(false, false); // preserve all swap files
|
ml_sync_all(false, false, true); // preserve all swap files
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -976,7 +976,7 @@ return {
|
|||||||
secure=true,
|
secure=true,
|
||||||
vi_def=true,
|
vi_def=true,
|
||||||
varname='p_fs',
|
varname='p_fs',
|
||||||
defaults={if_true={vi=true}}
|
defaults={if_true={vi=false}}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
full_name='gdefault', abbreviation='gd',
|
full_name='gdefault', abbreviation='gd',
|
||||||
|
@ -629,6 +629,7 @@ int os_fsync(int fd)
|
|||||||
{
|
{
|
||||||
int r;
|
int r;
|
||||||
RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
|
RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
|
||||||
|
g_stats.fsync++;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data)
|
|||||||
case SIGPWR:
|
case SIGPWR:
|
||||||
// Signal of a power failure(eg batteries low), flush the swap files to
|
// Signal of a power failure(eg batteries low), flush the swap files to
|
||||||
// be safe
|
// be safe
|
||||||
ml_sync_all(false, false);
|
ml_sync_all(false, false, true);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef SIGPIPE
|
#ifdef SIGPIPE
|
||||||
|
@ -811,7 +811,7 @@ static int open_shada_file_for_reading(const char *const fname,
|
|||||||
/// Wrapper for closing file descriptors
|
/// Wrapper for closing file descriptors
|
||||||
static void close_file(void *cookie)
|
static void close_file(void *cookie)
|
||||||
{
|
{
|
||||||
const int error = file_free(cookie, true);
|
const int error = file_free(cookie, !!p_fs);
|
||||||
if (error != 0) {
|
if (error != 0) {
|
||||||
emsgf(_(SERR "System error while closing ShaDa file: %s"),
|
emsgf(_(SERR "System error while closing ShaDa file: %s"),
|
||||||
os_strerror(error));
|
os_strerror(error));
|
||||||
|
68
test/functional/core/fileio_spec.lua
Normal file
68
test/functional/core/fileio_spec.lua
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local clear = helpers.clear
|
||||||
|
local command = helpers.command
|
||||||
|
local eq = helpers.eq
|
||||||
|
local feed = helpers.feed
|
||||||
|
local funcs = helpers.funcs
|
||||||
|
local nvim_prog = helpers.nvim_prog
|
||||||
|
local request = helpers.request
|
||||||
|
local retry = helpers.retry
|
||||||
|
local rmdir = helpers.rmdir
|
||||||
|
local sleep = helpers.sleep
|
||||||
|
|
||||||
|
describe('fileio', function()
|
||||||
|
before_each(function()
|
||||||
|
end)
|
||||||
|
after_each(function()
|
||||||
|
command(':qall!')
|
||||||
|
os.remove('Xtest_startup_shada')
|
||||||
|
os.remove('Xtest_startup_file1')
|
||||||
|
os.remove('Xtest_startup_file2')
|
||||||
|
rmdir('Xtest_startup_swapdir')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('fsync() codepaths #8304', function()
|
||||||
|
clear({ args={ '-i', 'Xtest_startup_shada',
|
||||||
|
'--cmd', 'set directory=Xtest_startup_swapdir' } })
|
||||||
|
|
||||||
|
-- These cases ALWAYS force fsync (regardless of 'fsync' option):
|
||||||
|
|
||||||
|
-- 1. Idle (CursorHold) with modified buffers (+ 'swapfile').
|
||||||
|
command('write Xtest_startup_file1')
|
||||||
|
feed('ifoo<esc>h')
|
||||||
|
command('write')
|
||||||
|
eq(0, request('nvim__stats').fsync) -- 'nofsync' is the default.
|
||||||
|
command('set swapfile')
|
||||||
|
command('set updatetime=1')
|
||||||
|
feed('izub<esc>h') -- File is 'modified'.
|
||||||
|
sleep(3) -- Allow 'updatetime' to expire.
|
||||||
|
retry(3, nil, function()
|
||||||
|
eq(1, request('nvim__stats').fsync)
|
||||||
|
end)
|
||||||
|
command('set updatetime=9999')
|
||||||
|
|
||||||
|
-- 2. Exit caused by deadly signal (+ 'swapfile').
|
||||||
|
local j = funcs.jobstart({ nvim_prog, '-u', 'NONE', '-i',
|
||||||
|
'Xtest_startup_shada', '--headless',
|
||||||
|
'-c', 'set swapfile',
|
||||||
|
'-c', 'write Xtest_startup_file2',
|
||||||
|
'-c', 'put =localtime()', })
|
||||||
|
sleep(10) -- Let Nvim start.
|
||||||
|
funcs.jobstop(j) -- Send deadly signal.
|
||||||
|
|
||||||
|
-- 3. SIGPWR signal.
|
||||||
|
-- ??
|
||||||
|
|
||||||
|
-- 4. Explicit :preserve command.
|
||||||
|
command('preserve')
|
||||||
|
eq(2, request('nvim__stats').fsync)
|
||||||
|
|
||||||
|
-- 5. Enable 'fsync' option, write file.
|
||||||
|
command('set fsync')
|
||||||
|
feed('ibaz<esc>h')
|
||||||
|
command('write')
|
||||||
|
eq(4, request('nvim__stats').fsync)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user