mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +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. | 	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,19 +1612,20 @@ 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; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * sync one buffer, including negative blocks |  * 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 |  * 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) | ||||||
|  |  | ||||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes