Mercurial > vim
comparison src/userfunc.c @ 16003:879829e44091 v8.1.1007
patch 8.1.1007: using closure may consume a lot of memory
commit https://github.com/vim/vim/commit/209b8e3e3bf7a4a3d102134124120f6c7f57d560
Author: Bram Moolenaar <Bram@vim.org>
Date: Thu Mar 14 13:43:24 2019 +0100
patch 8.1.1007: using closure may consume a lot of memory
Problem: Using closure may consume a lot of memory.
Solution: unreference items that are no longer needed. Add a test. (Ozaki
Kiichi, closes #3961)
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Thu, 14 Mar 2019 13:45:06 +0100 |
parents | bd75c9df2a14 |
children | 3d6b282e2d6e |
comparison
equal
deleted
inserted
replaced
16002:7834d7d14b48 | 16003:879829e44091 |
---|---|
37 static hashtab_T func_hashtab; | 37 static hashtab_T func_hashtab; |
38 | 38 |
39 /* Used by get_func_tv() */ | 39 /* Used by get_func_tv() */ |
40 static garray_T funcargs = GA_EMPTY; | 40 static garray_T funcargs = GA_EMPTY; |
41 | 41 |
42 /* pointer to funccal for currently active function */ | 42 // pointer to funccal for currently active function |
43 funccall_T *current_funccal = NULL; | 43 static funccall_T *current_funccal = NULL; |
44 | 44 |
45 /* Pointer to list of previously used funccal, still around because some | 45 // Pointer to list of previously used funccal, still around because some |
46 * item in it is still being used. */ | 46 // item in it is still being used. |
47 funccall_T *previous_funccal = NULL; | 47 static funccall_T *previous_funccal = NULL; |
48 | 48 |
49 static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); | 49 static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it"); |
50 static char *e_funcdict = N_("E717: Dictionary entry already exists"); | 50 static char *e_funcdict = N_("E717: Dictionary entry already exists"); |
51 static char *e_funcref = N_("E718: Funcref required"); | 51 static char *e_funcref = N_("E718: Funcref required"); |
52 static char *e_nofunc = N_("E130: Unknown function: %s"); | 52 static char *e_nofunc = N_("E130: Unknown function: %s"); |
584 v->di_tv.v_lock = VAR_FIXED; | 584 v->di_tv.v_lock = VAR_FIXED; |
585 v->di_tv.vval.v_number = nr; | 585 v->di_tv.vval.v_number = nr; |
586 } | 586 } |
587 | 587 |
588 /* | 588 /* |
589 * Free "fc" and what it contains. | 589 * Free "fc". |
590 */ | 590 */ |
591 static void | 591 static void |
592 free_funccal( | 592 free_funccal(funccall_T *fc) |
593 funccall_T *fc, | 593 { |
594 int free_val) /* a: vars were allocated */ | 594 int i; |
595 { | |
596 listitem_T *li; | |
597 int i; | |
598 | 595 |
599 for (i = 0; i < fc->fc_funcs.ga_len; ++i) | 596 for (i = 0; i < fc->fc_funcs.ga_len; ++i) |
600 { | 597 { |
601 ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; | 598 ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; |
602 | 599 |
603 /* When garbage collecting a funccall_T may be freed before the | 600 // When garbage collecting a funccall_T may be freed before the |
604 * function that references it, clear its uf_scoped field. | 601 // function that references it, clear its uf_scoped field. |
605 * The function may have been redefined and point to another | 602 // The function may have been redefined and point to another |
606 * funccall_T, don't clear it then. */ | 603 // funccall_T, don't clear it then. |
607 if (fp != NULL && fp->uf_scoped == fc) | 604 if (fp != NULL && fp->uf_scoped == fc) |
608 fp->uf_scoped = NULL; | 605 fp->uf_scoped = NULL; |
609 } | 606 } |
610 ga_clear(&fc->fc_funcs); | 607 ga_clear(&fc->fc_funcs); |
611 | 608 |
612 /* The a: variables typevals may not have been allocated, only free the | |
613 * allocated variables. */ | |
614 vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); | |
615 | |
616 /* free all l: variables */ | |
617 vars_clear(&fc->l_vars.dv_hashtab); | |
618 | |
619 /* Free the a:000 variables if they were allocated. */ | |
620 if (free_val) | |
621 for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) | |
622 clear_tv(&li->li_tv); | |
623 | |
624 func_ptr_unref(fc->func); | 609 func_ptr_unref(fc->func); |
625 vim_free(fc); | 610 vim_free(fc); |
626 } | 611 } |
627 | 612 |
628 /* | 613 /* |
614 * Free "fc" and what it contains. | |
615 * Can be called only when "fc" is kept beyond the period of it called, | |
616 * i.e. after cleanup_function_call(fc). | |
617 */ | |
618 static void | |
619 free_funccal_contents(funccall_T *fc) | |
620 { | |
621 listitem_T *li; | |
622 | |
623 // Free all l: variables. | |
624 vars_clear(&fc->l_vars.dv_hashtab); | |
625 | |
626 // Free all a: variables. | |
627 vars_clear(&fc->l_avars.dv_hashtab); | |
628 | |
629 // Free the a:000 variables. | |
630 for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) | |
631 clear_tv(&li->li_tv); | |
632 | |
633 free_funccal(fc); | |
634 } | |
635 | |
636 /* | |
629 * Handle the last part of returning from a function: free the local hashtable. | 637 * Handle the last part of returning from a function: free the local hashtable. |
630 * Unless it is still in use by a closure. | 638 * Unless it is still in use by a closure. |
631 */ | 639 */ |
632 static void | 640 static void |
633 cleanup_function_call(funccall_T *fc) | 641 cleanup_function_call(funccall_T *fc) |
634 { | 642 { |
643 int may_free_fc = fc->fc_refcount <= 0; | |
644 int free_fc = TRUE; | |
645 | |
635 current_funccal = fc->caller; | 646 current_funccal = fc->caller; |
636 | 647 |
637 /* If the a:000 list and the l: and a: dicts are not referenced and there | 648 // Free all l: variables if not referred. |
638 * is no closure using it, we can free the funccall_T and what's in it. */ | 649 if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) |
639 if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT | 650 vars_clear(&fc->l_vars.dv_hashtab); |
640 && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT | |
641 && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT | |
642 && fc->fc_refcount <= 0) | |
643 { | |
644 free_funccal(fc, FALSE); | |
645 } | |
646 else | 651 else |
647 { | 652 free_fc = FALSE; |
648 hashitem_T *hi; | 653 |
649 listitem_T *li; | 654 // If the a:000 list and the l: and a: dicts are not referenced and |
650 int todo; | 655 // there is no closure using it, we can free the funccall_T and what's |
651 dictitem_T *v; | 656 // in it. |
652 static int made_copy = 0; | 657 if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) |
653 | 658 vars_clear_ext(&fc->l_avars.dv_hashtab, FALSE); |
654 /* "fc" is still in use. This can happen when returning "a:000", | 659 else |
655 * assigning "l:" to a global variable or defining a closure. | 660 { |
656 * Link "fc" in the list for garbage collection later. */ | 661 int todo; |
657 fc->caller = previous_funccal; | 662 hashitem_T *hi; |
658 previous_funccal = fc; | 663 dictitem_T *di; |
659 | 664 |
660 /* Make a copy of the a: variables, since we didn't do that above. */ | 665 free_fc = FALSE; |
666 | |
667 // Make a copy of the a: variables, since we didn't do that above. | |
661 todo = (int)fc->l_avars.dv_hashtab.ht_used; | 668 todo = (int)fc->l_avars.dv_hashtab.ht_used; |
662 for (hi = fc->l_avars.dv_hashtab.ht_array; todo > 0; ++hi) | 669 for (hi = fc->l_avars.dv_hashtab.ht_array; todo > 0; ++hi) |
663 { | 670 { |
664 if (!HASHITEM_EMPTY(hi)) | 671 if (!HASHITEM_EMPTY(hi)) |
665 { | 672 { |
666 --todo; | 673 --todo; |
667 v = HI2DI(hi); | 674 di = HI2DI(hi); |
668 copy_tv(&v->di_tv, &v->di_tv); | 675 copy_tv(&di->di_tv, &di->di_tv); |
669 } | 676 } |
670 } | 677 } |
671 | 678 } |
672 /* Make a copy of the a:000 items, since we didn't do that above. */ | 679 |
680 if (may_free_fc && fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT) | |
681 fc->l_varlist.lv_first = NULL; | |
682 else | |
683 { | |
684 listitem_T *li; | |
685 | |
686 free_fc = FALSE; | |
687 | |
688 // Make a copy of the a:000 items, since we didn't do that above. | |
673 for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) | 689 for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) |
674 copy_tv(&li->li_tv, &li->li_tv); | 690 copy_tv(&li->li_tv, &li->li_tv); |
675 | 691 } |
676 if (++made_copy == 10000) | 692 |
677 { | 693 if (free_fc) |
678 // We have made a lot of copies. This can happen when | 694 free_funccal(fc); |
679 // repetitively calling a function that creates a reference to | 695 else |
696 { | |
697 static int made_copy = 0; | |
698 | |
699 // "fc" is still in use. This can happen when returning "a:000", | |
700 // assigning "l:" to a global variable or defining a closure. | |
701 // Link "fc" in the list for garbage collection later. | |
702 fc->caller = previous_funccal; | |
703 previous_funccal = fc; | |
704 | |
705 if (want_garbage_collect) | |
706 // If garbage collector is ready, clear count. | |
707 made_copy = 0; | |
708 else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) | |
709 { | |
710 // We have made a lot of copies, worth 4 Mbyte. This can happen | |
711 // when repetitively calling a function that creates a reference to | |
680 // itself somehow. Call the garbage collector soon to avoid using | 712 // itself somehow. Call the garbage collector soon to avoid using |
681 // too much memory. | 713 // too much memory. |
682 made_copy = 0; | 714 made_copy = 0; |
683 want_garbage_collect = TRUE; | 715 want_garbage_collect = TRUE; |
684 } | 716 } |
729 } | 761 } |
730 ++depth; | 762 ++depth; |
731 | 763 |
732 line_breakcheck(); /* check for CTRL-C hit */ | 764 line_breakcheck(); /* check for CTRL-C hit */ |
733 | 765 |
734 fc = (funccall_T *)alloc(sizeof(funccall_T)); | 766 fc = (funccall_T *)alloc_clear(sizeof(funccall_T)); |
735 if (fc == NULL) | 767 if (fc == NULL) |
736 return; | 768 return; |
737 fc->caller = current_funccal; | 769 fc->caller = current_funccal; |
738 current_funccal = fc; | 770 current_funccal = fc; |
739 fc->func = fp; | 771 fc->func = fp; |
830 } | 862 } |
831 if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) | 863 if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) |
832 { | 864 { |
833 v = &fc->fixvar[fixvar_idx++].var; | 865 v = &fc->fixvar[fixvar_idx++].var; |
834 v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; | 866 v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; |
867 STRCPY(v->di_key, name); | |
835 } | 868 } |
836 else | 869 else |
837 { | 870 { |
838 v = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) | 871 v = dictitem_alloc(name); |
839 + STRLEN(name))); | |
840 if (v == NULL) | 872 if (v == NULL) |
841 break; | 873 break; |
842 v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; | 874 v->di_flags |= DI_FLAGS_RO | DI_FLAGS_FIX; |
843 } | 875 } |
844 STRCPY(v->di_key, name); | |
845 | 876 |
846 /* Note: the values are copied directly to avoid alloc/free. | 877 /* Note: the values are copied directly to avoid alloc/free. |
847 * "argvars" must have VAR_FIXED for v_lock. */ | 878 * "argvars" must have VAR_FIXED for v_lock. */ |
848 v->di_tv = argvars[i]; | 879 v->di_tv = argvars[i]; |
849 v->di_tv.v_lock = VAR_FIXED; | 880 v->di_tv.v_lock = VAR_FIXED; |
858 else | 889 else |
859 hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); | 890 hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); |
860 | 891 |
861 if (ai >= 0 && ai < MAX_FUNC_ARGS) | 892 if (ai >= 0 && ai < MAX_FUNC_ARGS) |
862 { | 893 { |
863 list_append(&fc->l_varlist, &fc->l_listitems[ai]); | 894 listitem_T *li = &fc->l_listitems[ai]; |
864 fc->l_listitems[ai].li_tv = argvars[i]; | 895 |
865 fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; | 896 li->li_tv = argvars[i]; |
897 li->li_tv.v_lock = VAR_FIXED; | |
898 list_append(&fc->l_varlist, li); | |
866 } | 899 } |
867 } | 900 } |
868 | 901 |
869 /* Don't redraw while executing the function. */ | 902 /* Don't redraw while executing the function. */ |
870 ++RedrawingDisabled; | 903 ++RedrawingDisabled; |
1086 for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) | 1119 for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) |
1087 { | 1120 { |
1088 if (fc == *pfc) | 1121 if (fc == *pfc) |
1089 { | 1122 { |
1090 *pfc = fc->caller; | 1123 *pfc = fc->caller; |
1091 free_funccal(fc, TRUE); | 1124 free_funccal_contents(fc); |
1092 return; | 1125 return; |
1093 } | 1126 } |
1094 } | 1127 } |
1095 for (i = 0; i < fc->fc_funcs.ga_len; ++i) | 1128 for (i = 0; i < fc->fc_funcs.ga_len; ++i) |
1096 if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) | 1129 if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) |
3644 { | 3677 { |
3645 if (can_free_funccal(*pfc, copyID)) | 3678 if (can_free_funccal(*pfc, copyID)) |
3646 { | 3679 { |
3647 fc = *pfc; | 3680 fc = *pfc; |
3648 *pfc = fc->caller; | 3681 *pfc = fc->caller; |
3649 free_funccal(fc, TRUE); | 3682 free_funccal_contents(fc); |
3650 did_free = TRUE; | 3683 did_free = TRUE; |
3651 did_free_funccal = TRUE; | 3684 did_free_funccal = TRUE; |
3652 } | 3685 } |
3653 else | 3686 else |
3654 pfc = &(*pfc)->caller; | 3687 pfc = &(*pfc)->caller; |