Merge pull request #10905 from erw7/vim-8.1.0475

vim-patch:8.1.{475,800,868,1007,1027,1031,1033,1037,1058,1435,1484,1485}
This commit is contained in:
Matthieu Coudron 2020-05-25 14:01:36 +02:00 committed by GitHub
commit 8c588246a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 326 additions and 87 deletions

View File

@ -444,7 +444,7 @@ void eval_clear(void)
// unreferenced lists and dicts // unreferenced lists and dicts
(void)garbage_collect(false); (void)garbage_collect(false);
// functions // functions not garbage collected
free_all_functions(); free_all_functions();
} }
@ -873,17 +873,19 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert)
char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox)
{ {
char_u *retval; char_u *retval;
void *save_funccalp; funccal_entry_T funccal_entry;
save_funccalp = save_funccal(); save_funccal(&funccal_entry);
if (use_sandbox) if (use_sandbox) {
++sandbox; sandbox++;
++textlock; }
retval = eval_to_string(arg, nextcmd, FALSE); textlock++;
if (use_sandbox) retval = eval_to_string(arg, nextcmd, false);
--sandbox; if (use_sandbox) {
--textlock; sandbox--;
restore_funccal(save_funccalp); }
textlock--;
restore_funccal();
return retval; return retval;
} }
@ -5026,7 +5028,7 @@ bool garbage_collect(bool testing)
// 3. Check if any funccal can be freed now. // 3. Check if any funccal can be freed now.
// This may call us back recursively. // This may call us back recursively.
did_free = did_free || free_unref_funccal(copyID, testing); did_free = free_unref_funccal(copyID, testing) || did_free;
} else if (p_verbose > 0) { } else if (p_verbose > 0) {
verb_msg(_( verb_msg(_(
"Not enough memory to set references, garbage collection aborted!")); "Not enough memory to set references, garbage collection aborted!"));
@ -9766,9 +9768,11 @@ const void *var_shada_iter(const void *const iter, const char **const name,
void var_set_global(const char *const name, typval_T vartv) void var_set_global(const char *const name, typval_T vartv)
{ {
funccall_T *const saved_funccal = (funccall_T *)save_funccal(); funccal_entry_T funccall_entry;
save_funccal(&funccall_entry);
set_var(name, strlen(name), &vartv, false); set_var(name, strlen(name), &vartv, false);
restore_funccal(saved_funccal); restore_funccal();
} }
int store_session_globals(FILE *fd) int store_session_globals(FILE *fd)
@ -10324,8 +10328,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
.autocmd_fname = autocmd_fname, .autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match, .autocmd_match = autocmd_match,
.autocmd_bufnr = autocmd_bufnr, .autocmd_bufnr = autocmd_bufnr,
.funccalp = save_funccal() .funccalp = (void *)get_current_funccal()
}; };
funccal_entry_T funccal_entry;
save_funccal(&funccal_entry);
provider_call_nesting++; provider_call_nesting++;
typval_T argvars[3] = { typval_T argvars[3] = {
@ -10352,7 +10358,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
tv_list_unref(arguments); tv_list_unref(arguments);
// Restore caller scope information // Restore caller scope information
restore_funccal(provider_caller_scope.funccalp); restore_funccal();
provider_caller_scope = saved_provider_caller_scope; provider_caller_scope = saved_provider_caller_scope;
provider_call_nesting--; provider_call_nesting--;
assert(provider_call_nesting >= 0); assert(provider_call_nesting >= 0);

View File

@ -7243,7 +7243,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
linenr_T save_sourcing_lnum; linenr_T save_sourcing_lnum;
int save_autocmd_bufnr; int save_autocmd_bufnr;
void *save_funccalp; funccal_entry_T funccal_entry;
if (l_provider_call_nesting) { if (l_provider_call_nesting) {
// If this is called from a provider function, restore the scope // If this is called from a provider function, restore the scope
@ -7254,7 +7254,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
save_autocmd_fname = autocmd_fname; save_autocmd_fname = autocmd_fname;
save_autocmd_match = autocmd_match; save_autocmd_match = autocmd_match;
save_autocmd_bufnr = autocmd_bufnr; save_autocmd_bufnr = autocmd_bufnr;
save_funccalp = save_funccal(); save_funccal(&funccal_entry);
current_sctx = provider_caller_scope.script_ctx; current_sctx = provider_caller_scope.script_ctx;
sourcing_name = provider_caller_scope.sourcing_name; sourcing_name = provider_caller_scope.sourcing_name;
@ -7262,7 +7262,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_fname = provider_caller_scope.autocmd_fname;
autocmd_match = provider_caller_scope.autocmd_match; autocmd_match = provider_caller_scope.autocmd_match;
autocmd_bufnr = provider_caller_scope.autocmd_bufnr; autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
restore_funccal(provider_caller_scope.funccalp); set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
} }
@ -7280,7 +7280,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
autocmd_fname = save_autocmd_fname; autocmd_fname = save_autocmd_fname;
autocmd_match = save_autocmd_match; autocmd_match = save_autocmd_match;
autocmd_bufnr = save_autocmd_bufnr; autocmd_bufnr = save_autocmd_bufnr;
restore_funccal(save_funccalp); restore_funccal();
} }
if (ERROR_SET(&err)) { if (ERROR_SET(&err)) {

View File

@ -607,7 +607,7 @@ _convert_one_value_regular_dict: {}
kMPConvDict); kMPConvDict);
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict, TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
tv->vval.v_dict->dv_hashtab.ht_used); tv->vval.v_dict->dv_hashtab.ht_used);
assert(saved_copyID != copyID && saved_copyID != copyID - 1); assert(saved_copyID != copyID);
_mp_push(*mpstack, ((MPConvStackVal) { _mp_push(*mpstack, ((MPConvStackVal) {
.tv = tv, .tv = tv,
.type = kMPConvDict, .type = kMPConvDict,

View File

@ -43,11 +43,11 @@ hashtab_T func_hashtab;
static garray_T funcargs = GA_EMPTY_INIT_VALUE; static garray_T funcargs = GA_EMPTY_INIT_VALUE;
// pointer to funccal for currently active function // pointer to funccal for currently active function
funccall_T *current_funccal = NULL; static funccall_T *current_funccal = NULL;
// Pointer to list of previously used funccal, still around because some // Pointer to list of previously used funccal, still around because some
// item in it is still being used. // item in it is still being used.
funccall_T *previous_funccal = NULL; static funccall_T *previous_funccal = NULL;
static char *e_funcexts = N_( static char *e_funcexts = N_(
"E122: Function %s already exists, add ! to replace it"); "E122: Function %s already exists, add ! to replace it");
@ -541,14 +541,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
v->di_tv.vval.v_number = nr; v->di_tv.vval.v_number = nr;
} }
/* // Free "fc"
* Free "fc" and what it contains. static void free_funccal(funccall_T *fc)
*/
static void
free_funccal(
funccall_T *fc,
int free_val // a: vars were allocated
)
{ {
for (int i = 0; i < fc->fc_funcs.ga_len; i++) { for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
@ -563,34 +557,74 @@ free_funccal(
} }
ga_clear(&fc->fc_funcs); ga_clear(&fc->fc_funcs);
// The a: variables typevals may not have been allocated, only free the func_ptr_unref(fc->func);
// allocated variables. xfree(fc);
vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); }
// Free "fc" and what it contains.
// Can be called only when "fc" is kept beyond the period of it called,
// i.e. after cleanup_function_call(fc).
static void free_funccal_contents(funccall_T *fc)
{
// Free all l: variables. // Free all l: variables.
vars_clear(&fc->l_vars.dv_hashtab); vars_clear(&fc->l_vars.dv_hashtab);
// Free the a:000 variables if they were allocated. // Free all a: variables.
if (free_val) { vars_clear(&fc->l_avars.dv_hashtab);
TV_LIST_ITER(&fc->l_varlist, li, {
tv_clear(TV_LIST_ITEM_TV(li));
});
}
func_ptr_unref(fc->func); // Free the a:000 variables.
xfree(fc); TV_LIST_ITER(&fc->l_varlist, li, {
tv_clear(TV_LIST_ITEM_TV(li));
});
free_funccal(fc);
} }
/// Handle the last part of returning from a function: free the local hashtable. /// Handle the last part of returning from a function: free the local hashtable.
/// Unless it is still in use by a closure. /// Unless it is still in use by a closure.
static void cleanup_function_call(funccall_T *fc) static void cleanup_function_call(funccall_T *fc)
{ {
bool may_free_fc = fc->fc_refcount <= 0;
bool free_fc = true;
current_funccal = fc->caller; current_funccal = fc->caller;
// If the a:000 list and the l: and a: dicts are not referenced and there // Free all l: variables if not referred.
// is no closure using it, we can free the funccall_T and what's in it. if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) {
if (!fc_referenced(fc)) { vars_clear(&fc->l_vars.dv_hashtab);
free_funccal(fc, false); } else {
free_fc = false;
}
// If the a:000 list and the l: and a: dicts are not referenced and
// there is no closure using it, we can free the funccall_T and what's
// in it.
if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
vars_clear_ext(&fc->l_avars.dv_hashtab, false);
} else {
free_fc = false;
// Make a copy of the a: variables, since we didn't do that above.
TV_DICT_ITER(&fc->l_avars, di, {
tv_copy(&di->di_tv, &di->di_tv);
});
}
if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
== DO_NOT_FREE_CNT) {
fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
} else {
free_fc = false;
// Make a copy of the a:000 items, since we didn't do that above.
TV_LIST_ITER(&fc->l_varlist, li, {
tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
});
}
if (free_fc) {
free_funccal(fc);
} else { } else {
static int made_copy = 0; static int made_copy = 0;
@ -600,19 +634,12 @@ static void cleanup_function_call(funccall_T *fc)
fc->caller = previous_funccal; fc->caller = previous_funccal;
previous_funccal = fc; previous_funccal = fc;
// Make a copy of the a: variables, since we didn't do that above. if (want_garbage_collect) {
TV_DICT_ITER(&fc->l_avars, di, { // If garbage collector is ready, clear count.
tv_copy(&di->di_tv, &di->di_tv); made_copy = 0;
}); } else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) {
// We have made a lot of copies, worth 4 Mbyte. This can happen
// Make a copy of the a:000 items, since we didn't do that above. // when repetitively calling a function that creates a reference to
TV_LIST_ITER(&fc->l_varlist, li, {
tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
});
if (++made_copy == 10000) {
// We have made a lot of copies. This can happen when
// repetitively calling a function that creates a reference to
// itself somehow. Call the garbage collector soon to avoid using // itself somehow. Call the garbage collector soon to avoid using
// too much memory. // too much memory.
made_copy = 0; made_copy = 0;
@ -639,7 +666,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
if (fc == *pfc) { if (fc == *pfc) {
*pfc = fc->caller; *pfc = fc->caller;
free_funccal(fc, true); free_funccal_contents(fc);
return; return;
} }
} }
@ -766,7 +793,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
// check for CTRL-C hit // check for CTRL-C hit
line_breakcheck(); line_breakcheck();
// prepare the funccall_T structure // prepare the funccall_T structure
fc = xmalloc(sizeof(funccall_T)); fc = xcalloc(1, sizeof(funccall_T));
fc->caller = current_funccal; fc->caller = current_funccal;
current_funccal = fc; current_funccal = fc;
fc->func = fp; fc->func = fp;
@ -881,9 +908,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
} }
if (ai >= 0 && ai < MAX_FUNC_ARGS) { if (ai >= 0 && ai < MAX_FUNC_ARGS) {
tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); listitem_T *li = &fc->l_listitems[ai];
*TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i];
TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; *TV_LIST_ITEM_TV(li) = argvars[i];
TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED;
tv_list_append(&fc->l_varlist, li);
} }
} }
@ -1106,21 +1135,26 @@ static bool func_name_refcount(char_u *name)
return isdigit(*name) || *name == '<'; return isdigit(*name) || *name == '<';
} }
/* static funccal_entry_T *funccal_stack = NULL;
* Save the current function call pointer, and set it to NULL.
* Used when executing autocommands and for ":source".
*/
void *save_funccal(void)
{
funccall_T *fc = current_funccal;
// Save the current function call pointer, and set it to NULL.
// Used when executing autocommands and for ":source".
void save_funccal(funccal_entry_T *entry)
{
entry->top_funccal = current_funccal;
entry->next = funccal_stack;
funccal_stack = entry;
current_funccal = NULL; current_funccal = NULL;
return (void *)fc;
} }
void restore_funccal(void *vfc) void restore_funccal(void)
{ {
current_funccal = (funccall_T *)vfc; if (funccal_stack == NULL) {
IEMSG("INTERNAL: restore_funccal()");
} else {
current_funccal = funccal_stack->top_funccal;
funccal_stack = funccal_stack->next;
}
} }
funccall_T *get_current_funccal(void) funccall_T *get_current_funccal(void)
@ -1128,6 +1162,11 @@ funccall_T *get_current_funccal(void)
return current_funccal; return current_funccal;
} }
void set_current_funccal(funccall_T *fc)
{
current_funccal = fc;
}
#if defined(EXITFREE) #if defined(EXITFREE)
void free_all_functions(void) void free_all_functions(void)
{ {
@ -1137,10 +1176,13 @@ void free_all_functions(void)
uint64_t todo = 1; uint64_t todo = 1;
uint64_t used; uint64_t used;
// Clean up the call stack. // Clean up the current_funccal chain and the funccal stack.
while (current_funccal != NULL) { while (current_funccal != NULL) {
tv_clear(current_funccal->rettv); tv_clear(current_funccal->rettv);
cleanup_function_call(current_funccal); cleanup_function_call(current_funccal);
if (current_funccal == NULL && funccal_stack != NULL) {
restore_funccal();
}
} }
// First clear what the functions contain. Since this may lower the // First clear what the functions contain. Since this may lower the
@ -3121,7 +3163,7 @@ bool free_unref_funccal(int copyID, int testing)
if (can_free_funccal(*pfc, copyID)) { if (can_free_funccal(*pfc, copyID)) {
funccall_T *fc = *pfc; funccall_T *fc = *pfc;
*pfc = fc->caller; *pfc = fc->caller;
free_funccal(fc, true); free_funccal_contents(fc);
did_free = true; did_free = true;
did_free_funccal = true; did_free_funccal = true;
} else { } else {
@ -3314,9 +3356,18 @@ bool set_ref_in_call_stack(int copyID)
bool abort = false; bool abort = false;
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) { for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL); abort = abort || set_ref_in_funccal(fc, copyID);
abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
} }
// Also go through the funccal_stack.
for (funccal_entry_T *entry = funccal_stack; entry != NULL;
entry = entry->next) {
for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL;
fc = fc->caller) {
abort = abort || set_ref_in_funccal(fc, copyID);
}
}
return abort; return abort;
} }

View File

@ -11,6 +11,12 @@ typedef struct {
dictitem_T *fd_di; ///< Dictionary item used. dictitem_T *fd_di; ///< Dictionary item used.
} funcdict_T; } funcdict_T;
typedef struct funccal_entry funccal_entry_T;
struct funccal_entry {
void *top_funccal;
funccal_entry_T *next;
};
/// errors for when calling a function /// errors for when calling a function
typedef enum { typedef enum {
ERROR_UNKNOWN = 0, ERROR_UNKNOWN = 0,

View File

@ -3107,7 +3107,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
int retval = FAIL; int retval = FAIL;
static scid_T last_current_SID = 0; static scid_T last_current_SID = 0;
static int last_current_SID_seq = 0; static int last_current_SID_seq = 0;
void *save_funccalp;
int save_debug_break_level = debug_break_level; int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL; scriptitem_T *si = NULL;
proftime_T wait_start; proftime_T wait_start;
@ -3228,7 +3227,8 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
// Don't use local function variables, if called from a function. // Don't use local function variables, if called from a function.
// Also starts profiling timer for nested script. // Also starts profiling timer for nested script.
save_funccalp = save_funccal(); funccal_entry_T funccalp_entry;
save_funccal(&funccalp_entry);
// Check if this script was sourced before to finds its SID. // Check if this script was sourced before to finds its SID.
// If it's new, generate a new SID. // If it's new, generate a new SID.
@ -3353,7 +3353,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
} }
current_sctx = save_current_sctx; current_sctx = save_current_sctx;
restore_funccal(save_funccalp); restore_funccal();
if (l_do_profiling == PROF_YES) { if (l_do_profiling == PROF_YES) {
prof_child_exit(&wait_start); // leaving a child now prof_child_exit(&wait_start); // leaving a child now
} }

View File

@ -6736,7 +6736,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
static int nesting = 0; static int nesting = 0;
AutoPatCmd patcmd; AutoPatCmd patcmd;
AutoPat *ap; AutoPat *ap;
void *save_funccalp;
char_u *save_cmdarg; char_u *save_cmdarg;
long save_cmdbang; long save_cmdbang;
static int filechangeshell_busy = FALSE; static int filechangeshell_busy = FALSE;
@ -6926,8 +6925,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
if (do_profiling == PROF_YES) if (do_profiling == PROF_YES)
prof_child_enter(&wait_time); /* doesn't count for the caller itself */ prof_child_enter(&wait_time); /* doesn't count for the caller itself */
/* Don't use local function variables, if called from a function */ // Don't use local function variables, if called from a function.
save_funccalp = save_funccal(); funccal_entry_T funccal_entry;
save_funccal(&funccal_entry);
/* /*
* When starting to execute autocommands, save the search patterns. * When starting to execute autocommands, save the search patterns.
@ -7016,9 +7016,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
autocmd_bufnr = save_autocmd_bufnr; autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match; autocmd_match = save_autocmd_match;
current_sctx = save_current_sctx; current_sctx = save_current_sctx;
restore_funccal(save_funccalp); restore_funccal();
if (do_profiling == PROF_YES) if (do_profiling == PROF_YES) {
prof_child_exit(&wait_time); prof_child_exit(&wait_time);
}
KeyTyped = save_KeyTyped; KeyTyped = save_KeyTyped;
xfree(fname); xfree(fname);
xfree(sfname); xfree(sfname);

View File

@ -44,6 +44,10 @@ if &lines < 24 || &columns < 80
qa! qa!
endif endif
if has('reltime')
let s:start_time = reltime()
endif
" Common with all tests on all systems. " Common with all tests on all systems.
source setup.vim source setup.vim
@ -100,6 +104,9 @@ endfunc
func RunTheTest(test) func RunTheTest(test)
echo 'Executing ' . a:test echo 'Executing ' . a:test
if has('reltime')
let func_start = reltime()
endif
" Avoid stopping at the "hit enter" prompt " Avoid stopping at the "hit enter" prompt
set nomore set nomore
@ -124,7 +131,11 @@ func RunTheTest(test)
endtry endtry
endif endif
call add(s:messages, 'Executing ' . a:test) let message = 'Executed ' . a:test
if has('reltime')
let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
endif
call add(s:messages, message)
let s:done += 1 let s:done += 1
if a:test =~ 'Test_nocatch_' if a:test =~ 'Test_nocatch_'
@ -230,6 +241,9 @@ func FinishTesting()
else else
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif endif
if has('reltime')
let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
endif
echo message echo message
call add(s:messages, message) call add(s:messages, message)
if s:fail > 0 if s:fail > 0

View File

@ -0,0 +1,161 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eval = helpers.eval
local eq = helpers.eq
local feed_command = helpers.feed_command
local iswin = helpers.iswin
local retry = helpers.retry
local ok = helpers.ok
local source = helpers.source
local monitor_memory_usage = {
memory_usage = function(self)
local handle
if iswin() then
handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize')
else
handle = io.popen('ps -o rss= -p '..self.pid)
end
return tonumber(handle:read('*a'):match('%d+'))
end,
op = function(self)
retry(nil, 10000, function()
local val = self.memory_usage(self)
if self.max < val then
self.max = val
end
table.insert(self.hist, val)
ok(#self.hist > 20)
local result = {}
for key,value in ipairs(self.hist) do
if value ~= self.hist[key + 1] then
table.insert(result, value)
end
end
table.remove(self.hist, 1)
self.last = self.hist[#self.hist]
eq(#result, 1)
end)
end,
dump = function(self)
return 'max: '..self.max ..', last: '..self.last
end,
monitor_memory_usage = function(self, pid)
local obj = {
pid = pid,
max = 0,
last = 0,
hist = {},
}
setmetatable(obj, { __index = self })
obj:op()
return obj
end
}
setmetatable(monitor_memory_usage,
{__call = function(self, pid)
return monitor_memory_usage.monitor_memory_usage(self, pid)
end})
describe('memory usage', function()
local function check_result(tbl, status, result)
if not status then
print('')
for key, val in pairs(tbl) do
print(key, val:dump())
end
error(result)
end
end
local function isasan()
local version = eval('execute("version")')
return version:match('-fsanitize=[a-z,]*address')
end
before_each(clear)
--[[
Case: if a local variable captures a:000, funccall object will be free
just after it finishes.
]]--
it('function capture vargs', function()
if isasan() then
pending('ASAN build is difficult to estimate memory usage')
end
if iswin() and eval("executable('wmic')") == 0 then
pending('missing "wmic" command')
elseif eval("executable('ps')") == 0 then
pending('missing "ps" command')
end
local pid = eval('getpid()')
local before = monitor_memory_usage(pid)
source([[
func s:f(...)
let x = a:000
endfunc
for _ in range(10000)
call s:f(0)
endfor
]])
local after = monitor_memory_usage(pid)
-- Estimate the limit of max usage as 2x initial usage.
-- The lower limit can fluctuate a bit, use 97%.
check_result({before=before, after=after},
pcall(ok, before.last * 97 / 100 < after.max))
check_result({before=before, after=after},
pcall(ok, before.last * 2 > after.max))
-- In this case, garbage collecting is not needed.
-- The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
-- Based on various test runs.
local lower = after.last * 97 / 100
local upper = after.last * 105 / 100
check_result({before=before, after=after}, pcall(ok, lower < after.max))
check_result({before=before, after=after}, pcall(ok, after.max < upper))
end)
--[[
Case: if a local variable captures l: dict, funccall object will not be
free until garbage collector runs, but after that memory usage doesn't
increase so much even when rerun Xtest.vim since system memory caches.
]]--
it('function capture lvars', function()
if isasan() then
pending('ASAN build is difficult to estimate memory usage')
end
if iswin() and eval("executable('wmic')") == 0 then
pending('missing "wmic" command')
elseif eval("executable('ps')") == 0 then
pending('missing "ps" command')
end
local pid = eval('getpid()')
local before = monitor_memory_usage(pid)
local fname = source([[
if !exists('s:defined_func')
func s:f()
let x = l:
endfunc
endif
let s:defined_func = 1
for _ in range(10000)
call s:f()
endfor
]])
local after = monitor_memory_usage(pid)
for _ = 1, 3 do
feed_command('so '..fname)
end
local last = monitor_memory_usage(pid)
-- The usage may be a bit less than the last value, use 80%.
-- Allow for 20% tolerance at the upper limit. That's very permissive, but
-- otherwise the test fails sometimes.
local lower = before.last * 8 / 10
local upper = (after.max + (after.last - before.last)) * 12 / 10
check_result({before=before, after=after, last=last},
pcall(ok, lower < last.last))
check_result({before=before, after=after, last=last},
pcall(ok, last.last < upper))
end)
end)