mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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:
commit
8c588246a5
@ -444,7 +444,7 @@ void eval_clear(void)
|
||||
// unreferenced lists and dicts
|
||||
(void)garbage_collect(false);
|
||||
|
||||
// functions
|
||||
// functions not garbage collected
|
||||
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 *retval;
|
||||
void *save_funccalp;
|
||||
funccal_entry_T funccal_entry;
|
||||
|
||||
save_funccalp = save_funccal();
|
||||
if (use_sandbox)
|
||||
++sandbox;
|
||||
++textlock;
|
||||
retval = eval_to_string(arg, nextcmd, FALSE);
|
||||
if (use_sandbox)
|
||||
--sandbox;
|
||||
--textlock;
|
||||
restore_funccal(save_funccalp);
|
||||
save_funccal(&funccal_entry);
|
||||
if (use_sandbox) {
|
||||
sandbox++;
|
||||
}
|
||||
textlock++;
|
||||
retval = eval_to_string(arg, nextcmd, false);
|
||||
if (use_sandbox) {
|
||||
sandbox--;
|
||||
}
|
||||
textlock--;
|
||||
restore_funccal();
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -5026,7 +5028,7 @@ bool garbage_collect(bool testing)
|
||||
|
||||
// 3. Check if any funccal can be freed now.
|
||||
// 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) {
|
||||
verb_msg(_(
|
||||
"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)
|
||||
{
|
||||
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);
|
||||
restore_funccal(saved_funccal);
|
||||
restore_funccal();
|
||||
}
|
||||
|
||||
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_match = autocmd_match,
|
||||
.autocmd_bufnr = autocmd_bufnr,
|
||||
.funccalp = save_funccal()
|
||||
.funccalp = (void *)get_current_funccal()
|
||||
};
|
||||
funccal_entry_T funccal_entry;
|
||||
save_funccal(&funccal_entry);
|
||||
provider_call_nesting++;
|
||||
|
||||
typval_T argvars[3] = {
|
||||
@ -10352,7 +10358,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
|
||||
|
||||
tv_list_unref(arguments);
|
||||
// Restore caller scope information
|
||||
restore_funccal(provider_caller_scope.funccalp);
|
||||
restore_funccal();
|
||||
provider_caller_scope = saved_provider_caller_scope;
|
||||
provider_call_nesting--;
|
||||
assert(provider_call_nesting >= 0);
|
||||
|
@ -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;
|
||||
linenr_T save_sourcing_lnum;
|
||||
int save_autocmd_bufnr;
|
||||
void *save_funccalp;
|
||||
funccal_entry_T funccal_entry;
|
||||
|
||||
if (l_provider_call_nesting) {
|
||||
// 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_match = autocmd_match;
|
||||
save_autocmd_bufnr = autocmd_bufnr;
|
||||
save_funccalp = save_funccal();
|
||||
save_funccal(&funccal_entry);
|
||||
|
||||
current_sctx = provider_caller_scope.script_ctx;
|
||||
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_match = provider_caller_scope.autocmd_match;
|
||||
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_match = save_autocmd_match;
|
||||
autocmd_bufnr = save_autocmd_bufnr;
|
||||
restore_funccal(save_funccalp);
|
||||
restore_funccal();
|
||||
}
|
||||
|
||||
if (ERROR_SET(&err)) {
|
||||
|
@ -607,7 +607,7 @@ _convert_one_value_regular_dict: {}
|
||||
kMPConvDict);
|
||||
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
|
||||
tv->vval.v_dict->dv_hashtab.ht_used);
|
||||
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
|
||||
assert(saved_copyID != copyID);
|
||||
_mp_push(*mpstack, ((MPConvStackVal) {
|
||||
.tv = tv,
|
||||
.type = kMPConvDict,
|
||||
|
@ -43,11 +43,11 @@ hashtab_T func_hashtab;
|
||||
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
|
||||
|
||||
// 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
|
||||
// item in it is still being used.
|
||||
funccall_T *previous_funccal = NULL;
|
||||
static funccall_T *previous_funccal = NULL;
|
||||
|
||||
static char *e_funcexts = N_(
|
||||
"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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free "fc" and what it contains.
|
||||
*/
|
||||
static void
|
||||
free_funccal(
|
||||
funccall_T *fc,
|
||||
int free_val // a: vars were allocated
|
||||
)
|
||||
// Free "fc"
|
||||
static void free_funccal(funccall_T *fc)
|
||||
{
|
||||
for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
|
||||
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
|
||||
@ -563,34 +557,74 @@ free_funccal(
|
||||
}
|
||||
ga_clear(&fc->fc_funcs);
|
||||
|
||||
// The a: variables typevals may not have been allocated, only free the
|
||||
// allocated variables.
|
||||
vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
|
||||
func_ptr_unref(fc->func);
|
||||
xfree(fc);
|
||||
}
|
||||
|
||||
// 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.
|
||||
vars_clear(&fc->l_vars.dv_hashtab);
|
||||
|
||||
// Free the a:000 variables if they were allocated.
|
||||
if (free_val) {
|
||||
TV_LIST_ITER(&fc->l_varlist, li, {
|
||||
tv_clear(TV_LIST_ITEM_TV(li));
|
||||
});
|
||||
}
|
||||
// Free all a: variables.
|
||||
vars_clear(&fc->l_avars.dv_hashtab);
|
||||
|
||||
func_ptr_unref(fc->func);
|
||||
xfree(fc);
|
||||
// Free the a:000 variables.
|
||||
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.
|
||||
/// Unless it is still in use by a closure.
|
||||
static void cleanup_function_call(funccall_T *fc)
|
||||
{
|
||||
bool may_free_fc = fc->fc_refcount <= 0;
|
||||
bool free_fc = true;
|
||||
|
||||
current_funccal = fc->caller;
|
||||
|
||||
// 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 (!fc_referenced(fc)) {
|
||||
free_funccal(fc, false);
|
||||
// Free all l: variables if not referred.
|
||||
if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) {
|
||||
vars_clear(&fc->l_vars.dv_hashtab);
|
||||
} 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 {
|
||||
static int made_copy = 0;
|
||||
|
||||
@ -600,19 +634,12 @@ static void cleanup_function_call(funccall_T *fc)
|
||||
fc->caller = previous_funccal;
|
||||
previous_funccal = fc;
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
// 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 (++made_copy == 10000) {
|
||||
// We have made a lot of copies. This can happen when
|
||||
// repetitively calling a function that creates a reference to
|
||||
if (want_garbage_collect) {
|
||||
// If garbage collector is ready, clear count.
|
||||
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
|
||||
// when repetitively calling a function that creates a reference to
|
||||
// itself somehow. Call the garbage collector soon to avoid using
|
||||
// too much memory.
|
||||
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) {
|
||||
if (fc == *pfc) {
|
||||
*pfc = fc->caller;
|
||||
free_funccal(fc, true);
|
||||
free_funccal_contents(fc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -766,7 +793,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
|
||||
// check for CTRL-C hit
|
||||
line_breakcheck();
|
||||
// prepare the funccall_T structure
|
||||
fc = xmalloc(sizeof(funccall_T));
|
||||
fc = xcalloc(1, sizeof(funccall_T));
|
||||
fc->caller = current_funccal;
|
||||
current_funccal = fc;
|
||||
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) {
|
||||
tv_list_append(&fc->l_varlist, &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;
|
||||
listitem_T *li = &fc->l_listitems[ai];
|
||||
|
||||
*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 == '<';
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
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(funccal_entry_T *entry)
|
||||
{
|
||||
entry->top_funccal = current_funccal;
|
||||
entry->next = funccal_stack;
|
||||
funccal_stack = entry;
|
||||
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)
|
||||
@ -1128,6 +1162,11 @@ funccall_T *get_current_funccal(void)
|
||||
return current_funccal;
|
||||
}
|
||||
|
||||
void set_current_funccal(funccall_T *fc)
|
||||
{
|
||||
current_funccal = fc;
|
||||
}
|
||||
|
||||
#if defined(EXITFREE)
|
||||
void free_all_functions(void)
|
||||
{
|
||||
@ -1137,10 +1176,13 @@ void free_all_functions(void)
|
||||
uint64_t todo = 1;
|
||||
uint64_t used;
|
||||
|
||||
// Clean up the call stack.
|
||||
// Clean up the current_funccal chain and the funccal stack.
|
||||
while (current_funccal != NULL) {
|
||||
tv_clear(current_funccal->rettv);
|
||||
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
|
||||
@ -3121,7 +3163,7 @@ bool free_unref_funccal(int copyID, int testing)
|
||||
if (can_free_funccal(*pfc, copyID)) {
|
||||
funccall_T *fc = *pfc;
|
||||
*pfc = fc->caller;
|
||||
free_funccal(fc, true);
|
||||
free_funccal_contents(fc);
|
||||
did_free = true;
|
||||
did_free_funccal = true;
|
||||
} else {
|
||||
@ -3314,9 +3356,18 @@ bool set_ref_in_call_stack(int copyID)
|
||||
bool abort = false;
|
||||
|
||||
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_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
|
||||
abort = abort || set_ref_in_funccal(fc, copyID);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,12 @@ typedef struct {
|
||||
dictitem_T *fd_di; ///< Dictionary item used.
|
||||
} 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
|
||||
typedef enum {
|
||||
ERROR_UNKNOWN = 0,
|
||||
|
@ -3107,7 +3107,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
|
||||
int retval = FAIL;
|
||||
static scid_T last_current_SID = 0;
|
||||
static int last_current_SID_seq = 0;
|
||||
void *save_funccalp;
|
||||
int save_debug_break_level = debug_break_level;
|
||||
scriptitem_T *si = NULL;
|
||||
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.
|
||||
// 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.
|
||||
// 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;
|
||||
restore_funccal(save_funccalp);
|
||||
restore_funccal();
|
||||
if (l_do_profiling == PROF_YES) {
|
||||
prof_child_exit(&wait_start); // leaving a child now
|
||||
}
|
||||
|
@ -6736,7 +6736,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
|
||||
static int nesting = 0;
|
||||
AutoPatCmd patcmd;
|
||||
AutoPat *ap;
|
||||
void *save_funccalp;
|
||||
char_u *save_cmdarg;
|
||||
long save_cmdbang;
|
||||
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)
|
||||
prof_child_enter(&wait_time); /* doesn't count for the caller itself */
|
||||
|
||||
/* Don't use local function variables, if called from a function */
|
||||
save_funccalp = save_funccal();
|
||||
// Don't use local function variables, if called from a function.
|
||||
funccal_entry_T funccal_entry;
|
||||
save_funccal(&funccal_entry);
|
||||
|
||||
/*
|
||||
* 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_match = save_autocmd_match;
|
||||
current_sctx = save_current_sctx;
|
||||
restore_funccal(save_funccalp);
|
||||
if (do_profiling == PROF_YES)
|
||||
restore_funccal();
|
||||
if (do_profiling == PROF_YES) {
|
||||
prof_child_exit(&wait_time);
|
||||
}
|
||||
KeyTyped = save_KeyTyped;
|
||||
xfree(fname);
|
||||
xfree(sfname);
|
||||
|
@ -44,6 +44,10 @@ if &lines < 24 || &columns < 80
|
||||
qa!
|
||||
endif
|
||||
|
||||
if has('reltime')
|
||||
let s:start_time = reltime()
|
||||
endif
|
||||
|
||||
" Common with all tests on all systems.
|
||||
source setup.vim
|
||||
|
||||
@ -100,6 +104,9 @@ endfunc
|
||||
|
||||
func RunTheTest(test)
|
||||
echo 'Executing ' . a:test
|
||||
if has('reltime')
|
||||
let func_start = reltime()
|
||||
endif
|
||||
|
||||
" Avoid stopping at the "hit enter" prompt
|
||||
set nomore
|
||||
@ -124,7 +131,11 @@ func RunTheTest(test)
|
||||
endtry
|
||||
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
|
||||
|
||||
if a:test =~ 'Test_nocatch_'
|
||||
@ -230,6 +241,9 @@ func FinishTesting()
|
||||
else
|
||||
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
|
||||
endif
|
||||
if has('reltime')
|
||||
let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
|
||||
endif
|
||||
echo message
|
||||
call add(s:messages, message)
|
||||
if s:fail > 0
|
||||
|
161
test/functional/legacy/memory_usage_spec.lua
Normal file
161
test/functional/legacy/memory_usage_spec.lua
Normal 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)
|
Loading…
Reference in New Issue
Block a user