mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 09:44:31 +00:00 
			
		
		
		
	Merge #8304 "default to 'nofsync'"
This commit is contained in:
		@@ -2690,17 +2690,19 @@ A jump table for the options with a short description can be found at |Q_op|.
 | 
			
		||||
	security reasons.
 | 
			
		||||
 | 
			
		||||
						*'fsync'* *'fs'*
 | 
			
		||||
'fsync' 'fs'		boolean	(default on)
 | 
			
		||||
'fsync' 'fs'		boolean	(default off)
 | 
			
		||||
			global
 | 
			
		||||
	When on, the library function fsync() will be called after writing a
 | 
			
		||||
	file.  This will flush a file to disk, ensuring that it is safely
 | 
			
		||||
	written even on filesystems which do metadata-only journaling.  This
 | 
			
		||||
	will force the harddrive to spin up on Linux systems running in laptop
 | 
			
		||||
	mode, so it may be undesirable in some situations.  Be warned that
 | 
			
		||||
	turning this off increases the chances of data loss after a crash.
 | 
			
		||||
	When on, the OS function fsync() will be called after saving a file
 | 
			
		||||
	(|:write|, |writefile()|, …), |swap-file| and |shada-file|. This
 | 
			
		||||
	flushes the file to disk, ensuring that it is safely written.
 | 
			
		||||
	Slow on some systems: writing buffers, quitting Nvim, and other
 | 
			
		||||
	operations may sometimes take a few seconds.
 | 
			
		||||
 | 
			
		||||
	Currently applies only to writing the buffer with e.g. |:w| and 
 | 
			
		||||
	|writefile()|.
 | 
			
		||||
	Files are ALWAYS flushed ('fsync' is ignored) when:
 | 
			
		||||
	- |CursorHold| event is triggered
 | 
			
		||||
	- |:preserve| is called
 | 
			
		||||
	- system signals low battery life
 | 
			
		||||
	- Nvim exits abnormally
 | 
			
		||||
 | 
			
		||||
				   *'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
 | 
			
		||||
'gdefault' 'gd'		boolean	(default off)
 | 
			
		||||
 
 | 
			
		||||
@@ -1473,6 +1473,17 @@ Float nvim__id_float(Float 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.
 | 
			
		||||
///
 | 
			
		||||
/// @return Array of UI dictionaries
 | 
			
		||||
 
 | 
			
		||||
@@ -265,7 +265,7 @@ static void process_close_event(void **argv)
 | 
			
		||||
  if (proc->type == kProcessTypePty) {
 | 
			
		||||
    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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6552,18 +6552,14 @@ void alist_slash_adjust(void)
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * ":preserve".
 | 
			
		||||
 */
 | 
			
		||||
/// ":preserve".
 | 
			
		||||
static void ex_preserve(exarg_T *eap)
 | 
			
		||||
{
 | 
			
		||||
  curbuf->b_flags |= BF_PRESERVED;
 | 
			
		||||
  ml_preserve(curbuf, TRUE);
 | 
			
		||||
  ml_preserve(curbuf, true, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * ":recover".
 | 
			
		||||
 */
 | 
			
		||||
/// ":recover".
 | 
			
		||||
static void ex_recover(exarg_T *eap)
 | 
			
		||||
{
 | 
			
		||||
  /* Set recoverymode right away to avoid the ATTENTION prompt. */
 | 
			
		||||
 
 | 
			
		||||
@@ -3068,7 +3068,7 @@ nobackup:
 | 
			
		||||
   */
 | 
			
		||||
  if (reset_changed && !newfile && overwriting
 | 
			
		||||
      && !(exiting && backup != NULL)) {
 | 
			
		||||
    ml_preserve(buf, FALSE);
 | 
			
		||||
    ml_preserve(buf, false, !!p_fs);
 | 
			
		||||
    if (got_int) {
 | 
			
		||||
      SET_ERRMSG(_(e_interr));
 | 
			
		||||
      goto restore_backup;
 | 
			
		||||
 
 | 
			
		||||
@@ -1328,10 +1328,8 @@ int using_script(void)
 | 
			
		||||
  return scriptin[curscript] != NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * This function is called just before doing a blocking wait.  Thus after
 | 
			
		||||
 * waiting 'updatetime' for a character to arrive.
 | 
			
		||||
 */
 | 
			
		||||
/// This function is called just before doing a blocking wait.  Thus after
 | 
			
		||||
/// waiting 'updatetime' for a character to arrive.
 | 
			
		||||
void before_blocking(void)
 | 
			
		||||
{
 | 
			
		||||
  updatescript(0);
 | 
			
		||||
@@ -1340,21 +1338,22 @@ void before_blocking(void)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 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)
 | 
			
		||||
 *
 | 
			
		||||
 * All the changed memfiles are synced if c == 0 or when the number of typed
 | 
			
		||||
 * characters reaches 'updatecount' and 'updatecount' is non-zero.
 | 
			
		||||
 */
 | 
			
		||||
void updatescript(int c)
 | 
			
		||||
/// updatescript() is called when a character can be written to the script file
 | 
			
		||||
/// 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
 | 
			
		||||
/// characters reaches 'updatecount' and 'updatecount' is non-zero.
 | 
			
		||||
static void updatescript(int c)
 | 
			
		||||
{
 | 
			
		||||
  static int count = 0;
 | 
			
		||||
 | 
			
		||||
  if (c && scriptout)
 | 
			
		||||
  if (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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,11 @@ typedef enum {
 | 
			
		||||
  kTrue  = 1,
 | 
			
		||||
} TriState;
 | 
			
		||||
 | 
			
		||||
EXTERN struct nvim_stats_s {
 | 
			
		||||
  int64_t fsync;
 | 
			
		||||
  int64_t redraw;
 | 
			
		||||
} g_stats INIT(= { 0, 0 });
 | 
			
		||||
 | 
			
		||||
/* Values for "starting" */
 | 
			
		||||
#define NO_SCREEN       2       /* no screen updating 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
 | 
			
		||||
 * 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) {
 | 
			
		||||
    if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL)
 | 
			
		||||
@@ -1612,19 +1612,20 @@ void ml_sync_all(int check_file, int check_char)
 | 
			
		||||
      if (!os_fileinfo((char *)buf->b_ffname, &file_info)
 | 
			
		||||
          || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read
 | 
			
		||||
          || os_fileinfo_size(&file_info) != buf->b_orig_size) {
 | 
			
		||||
        ml_preserve(buf, FALSE);
 | 
			
		||||
        did_check_timestamps = FALSE;
 | 
			
		||||
        need_check_timestamps = TRUE;           /* give message later */
 | 
			
		||||
        ml_preserve(buf, false, do_fsync);
 | 
			
		||||
        did_check_timestamps = false;
 | 
			
		||||
        need_check_timestamps = true;           // give message later
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (buf->b_ml.ml_mfp->mf_dirty) {
 | 
			
		||||
      (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0)
 | 
			
		||||
          | (bufIsChanged(buf) ? MFS_FLUSH : 0));
 | 
			
		||||
      if (check_char && os_char_avail())        /* character available now */
 | 
			
		||||
                    | (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0));
 | 
			
		||||
      if (check_char && os_char_avail()) {      // character available now
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * sync one buffer, including negative blocks
 | 
			
		||||
@@ -1636,7 +1637,7 @@ void ml_sync_all(int check_file, int check_char)
 | 
			
		||||
 *
 | 
			
		||||
 * 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;
 | 
			
		||||
  linenr_T lnum;
 | 
			
		||||
@@ -1654,9 +1655,9 @@ void ml_preserve(buf_T *buf, int message)
 | 
			
		||||
   * before. */
 | 
			
		||||
  got_int = FALSE;
 | 
			
		||||
 | 
			
		||||
  ml_flush_line(buf);                               /* flush buffered line */
 | 
			
		||||
  (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);   /* flush locked block */
 | 
			
		||||
  status = mf_sync(mfp, MFS_ALL | MFS_FLUSH);
 | 
			
		||||
  ml_flush_line(buf);                               // flush buffered line
 | 
			
		||||
  (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);   // flush locked block
 | 
			
		||||
  status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0));
 | 
			
		||||
 | 
			
		||||
  /* stack is invalid after mf_sync(.., MFS_ALL) */
 | 
			
		||||
  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");
 | 
			
		||||
      lnum = buf->b_ml.ml_locked_high + 1;
 | 
			
		||||
    }
 | 
			
		||||
    (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);     /* flush locked block */
 | 
			
		||||
    /* sync the updated pointer blocks */
 | 
			
		||||
    if (mf_sync(mfp, MFS_ALL | MFS_FLUSH) == FAIL)
 | 
			
		||||
    (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);  // flush locked block
 | 
			
		||||
    // sync the updated pointer blocks
 | 
			
		||||
    if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == 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:
 | 
			
		||||
  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) {
 | 
			
		||||
      mch_errmsg((uint8_t *)"Vim: preserving files...\n");
 | 
			
		||||
      ui_flush();
 | 
			
		||||
      ml_sync_all(false, false);    // preserve all swap files
 | 
			
		||||
      ml_sync_all(false, false, true);  // preserve all swap files
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -976,7 +976,7 @@ return {
 | 
			
		||||
      secure=true,
 | 
			
		||||
      vi_def=true,
 | 
			
		||||
      varname='p_fs',
 | 
			
		||||
      defaults={if_true={vi=true}}
 | 
			
		||||
      defaults={if_true={vi=false}}
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      full_name='gdefault', abbreviation='gd',
 | 
			
		||||
 
 | 
			
		||||
@@ -629,6 +629,7 @@ int os_fsync(int fd)
 | 
			
		||||
{
 | 
			
		||||
  int r;
 | 
			
		||||
  RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL);
 | 
			
		||||
  g_stats.fsync++;
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -145,7 +145,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data)
 | 
			
		||||
    case SIGPWR:
 | 
			
		||||
      // Signal of a power failure(eg batteries low), flush the swap files to
 | 
			
		||||
      // be safe
 | 
			
		||||
      ml_sync_all(false, false);
 | 
			
		||||
      ml_sync_all(false, false, true);
 | 
			
		||||
      break;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef SIGPIPE
 | 
			
		||||
 
 | 
			
		||||
@@ -811,7 +811,7 @@ static int open_shada_file_for_reading(const char *const fname,
 | 
			
		||||
/// Wrapper for closing file descriptors
 | 
			
		||||
static void close_file(void *cookie)
 | 
			
		||||
{
 | 
			
		||||
  const int error = file_free(cookie, true);
 | 
			
		||||
  const int error = file_free(cookie, !!p_fs);
 | 
			
		||||
  if (error != 0) {
 | 
			
		||||
    emsgf(_(SERR "System error while closing ShaDa file: %s"),
 | 
			
		||||
          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)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user