diff options
| author | Nicolás Bértolo | 2020-05-19 15:57:31 -0300 |
|---|---|---|
| committer | Andrea Corallo | 2020-05-25 09:42:10 +0100 |
| commit | 1b809f378f6263bc099da45c5e4a42c89fef8d71 (patch) | |
| tree | 1adfe55b6b0761de4f12bc0830aaab7a3296bc07 /src/comp.c | |
| parent | 9daffe9cfe82d3b1e1e9fa8929dbb40cfed60f0f (diff) | |
| download | emacs-1b809f378f6263bc099da45c5e4a42c89fef8d71.tar.gz emacs-1b809f378f6263bc099da45c5e4a42c89fef8d71.zip | |
Improve handling of native compilation units still in use in Windows
When closing emacs will inspect all directories from which it loaded
native compilation units. If it finds a ".eln.old" file it will try to
delete it, if it fails that means that another Emacs instance is using it.
When compiling a file we rename the file that was in the output path
in case it has been loaded into another Emacs instance.
When deleting a package we move any ".eln" or ".eln.old" files in the
package folder that we can't delete to `package-user-dir`. Emacs will
check that directory when closing and delete them.
* lisp/emacs-lisp/comp.el (comp--replace-output-file): Function called
from C code to finish the compilation process. It performs renaming of
the old file if necessary.
* lisp/emacs-lisp/package.el (package--delete-directory): Function to
delete a package directory. It moves native compilation units that it
can't delete to `package-user-dir'.
* src/alloc.c (cleanup_vector): Call dispose_comp_unit().
(garbage_collect): Call finish_delayed_disposal_of_comp_units().
* src/comp.c: Restore the signal mask using unwind-protect. Store
loaded native compilation units in a hash table for disposal on
close. Store filenames of native compilation units GC'd in a linked
list to finish their disposal when the GC is over.
(clean_comp_unit_directory): Delete all *.eln.old files in a
directory.
(clean_package_user_dir_of_old_comp_units): Delete all *.eln.old files
in `package-user-dir'.
(dispose_all_remaining_comp_units): Dispose of native compilation
units that are still loaded.
(dispose_comp_unit): Close handle and cleanup directory or arrange for
later cleanup if DELAY is true.
(finish_delayed_disposal_of_comp_units): Dispose of native compilation
units that were GC'd.
(register_native_comp_unit): Register native compilation unit for
disposal when Emacs closes.
* src/comp.h: Introduce cfile member in Lisp_Native_Comp_Unit.
Add declarations of functions that: clean directories of unused native
compilation units, handle disposal of native compilation units.
* src/emacs.c (kill-emacs): Dispose all remaining compilation units
right right before calling exit().
* src/eval.c (internal_condition_case_3, internal_condition_case_4):
Add functions.
* src/lisp.h (internal_condition_case_3, internal_condition_case_4):
Add functions.
* src/pdumper.c (dump_do_dump_relocation): Set cfile to a copy of the
Lisp string specifying the file path.
Diffstat (limited to 'src/comp.c')
| -rw-r--r-- | src/comp.c | 260 |
1 files changed, 250 insertions, 10 deletions
diff --git a/src/comp.c b/src/comp.c index 68ad6d3eb8d..16ad77c74bc 100644 --- a/src/comp.c +++ b/src/comp.c | |||
| @@ -411,6 +411,10 @@ load_gccjit_if_necessary (bool mandatory) | |||
| 411 | #define CALL1I(fun, arg) \ | 411 | #define CALL1I(fun, arg) \ |
| 412 | CALLN (Ffuncall, intern_c_string (STR (fun)), arg) | 412 | CALLN (Ffuncall, intern_c_string (STR (fun)), arg) |
| 413 | 413 | ||
| 414 | /* Like call2 but stringify and intern. */ | ||
| 415 | #define CALL2I(fun, arg1, arg2) \ | ||
| 416 | CALLN (Ffuncall, intern_c_string (STR (fun)), arg1, arg2) | ||
| 417 | |||
| 414 | #define DECL_BLOCK(name, func) \ | 418 | #define DECL_BLOCK(name, func) \ |
| 415 | gcc_jit_block *(name) = \ | 419 | gcc_jit_block *(name) = \ |
| 416 | gcc_jit_function_new_block ((func), STR (name)) | 420 | gcc_jit_function_new_block ((func), STR (name)) |
| @@ -435,6 +439,8 @@ typedef struct { | |||
| 435 | ptrdiff_t size; | 439 | ptrdiff_t size; |
| 436 | } f_reloc_t; | 440 | } f_reloc_t; |
| 437 | 441 | ||
| 442 | sigset_t saved_sigset; | ||
| 443 | |||
| 438 | static f_reloc_t freloc; | 444 | static f_reloc_t freloc; |
| 439 | 445 | ||
| 440 | /* C side of the compiler context. */ | 446 | /* C side of the compiler context. */ |
| @@ -3795,6 +3801,13 @@ DEFUN ("comp--release-ctxt", Fcomp__release_ctxt, Scomp__release_ctxt, | |||
| 3795 | return Qt; | 3801 | return Qt; |
| 3796 | } | 3802 | } |
| 3797 | 3803 | ||
| 3804 | static void | ||
| 3805 | restore_sigmask (void) | ||
| 3806 | { | ||
| 3807 | pthread_sigmask (SIG_SETMASK, &saved_sigset, 0); | ||
| 3808 | unblock_input (); | ||
| 3809 | } | ||
| 3810 | |||
| 3798 | DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, | 3811 | DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, |
| 3799 | Scomp__compile_ctxt_to_file, | 3812 | Scomp__compile_ctxt_to_file, |
| 3800 | 1, 1, 0, | 3813 | 1, 1, 0, |
| @@ -3816,6 +3829,8 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, | |||
| 3816 | CALL1I (comp-data-container-idx, CALL1I (comp-ctxt-d-ephemeral, Vcomp_ctxt)); | 3829 | CALL1I (comp-data-container-idx, CALL1I (comp-ctxt-d-ephemeral, Vcomp_ctxt)); |
| 3817 | 3830 | ||
| 3818 | sigset_t oldset; | 3831 | sigset_t oldset; |
| 3832 | ptrdiff_t count = 0; | ||
| 3833 | |||
| 3819 | if (!noninteractive) | 3834 | if (!noninteractive) |
| 3820 | { | 3835 | { |
| 3821 | sigset_t blocked; | 3836 | sigset_t blocked; |
| @@ -3828,6 +3843,8 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, | |||
| 3828 | sigaddset (&blocked, SIGIO); | 3843 | sigaddset (&blocked, SIGIO); |
| 3829 | #endif | 3844 | #endif |
| 3830 | pthread_sigmask (SIG_BLOCK, &blocked, &oldset); | 3845 | pthread_sigmask (SIG_BLOCK, &blocked, &oldset); |
| 3846 | count = SPECPDL_INDEX (); | ||
| 3847 | record_unwind_protect_void (restore_sigmask); | ||
| 3831 | } | 3848 | } |
| 3832 | emit_ctxt_code (); | 3849 | emit_ctxt_code (); |
| 3833 | 3850 | ||
| @@ -3866,18 +3883,10 @@ DEFUN ("comp--compile-ctxt-to-file", Fcomp__compile_ctxt_to_file, | |||
| 3866 | GCC_JIT_OUTPUT_KIND_DYNAMIC_LIBRARY, | 3883 | GCC_JIT_OUTPUT_KIND_DYNAMIC_LIBRARY, |
| 3867 | SSDATA (tmp_file)); | 3884 | SSDATA (tmp_file)); |
| 3868 | 3885 | ||
| 3869 | /* Remove the old eln instead of copying the new one into it to get | 3886 | CALL2I(comp--replace-output-file, out_file, tmp_file); |
| 3870 | a new inode and prevent crashes in case the old one is currently | ||
| 3871 | loaded. */ | ||
| 3872 | if (!NILP (Ffile_exists_p (out_file))) | ||
| 3873 | Fdelete_file (out_file, Qnil); | ||
| 3874 | Frename_file (tmp_file, out_file, Qnil); | ||
| 3875 | 3887 | ||
| 3876 | if (!noninteractive) | 3888 | if (!noninteractive) |
| 3877 | { | 3889 | unbind_to (count, Qnil); |
| 3878 | pthread_sigmask (SIG_SETMASK, &oldset, 0); | ||
| 3879 | unblock_input (); | ||
| 3880 | } | ||
| 3881 | 3890 | ||
| 3882 | return out_file; | 3891 | return out_file; |
| 3883 | } | 3892 | } |
| @@ -3939,6 +3948,223 @@ helper_PSEUDOVECTOR_TYPEP_XUNTAG (Lisp_Object a, enum pvec_type code) | |||
| 3939 | } | 3948 | } |
| 3940 | 3949 | ||
| 3941 | 3950 | ||
| 3951 | /*********************************/ | ||
| 3952 | /* Disposal of compilation units */ | ||
| 3953 | /*********************************/ | ||
| 3954 | |||
| 3955 | /* | ||
| 3956 | The problem: Windows does not let us delete an .eln file that has been | ||
| 3957 | loaded by a process. This has two implications in Emacs: | ||
| 3958 | |||
| 3959 | 1) It is not possible to recompile a lisp file if the corresponding | ||
| 3960 | .eln file has been loaded. This is because we'd like to use the same | ||
| 3961 | filename, but we can't delete the old .eln file. | ||
| 3962 | |||
| 3963 | 2) It is not possible to delete a package using `package-delete' | ||
| 3964 | if an .eln file has been loaded. | ||
| 3965 | |||
| 3966 | * General idea | ||
| 3967 | |||
| 3968 | The solution to these two problems is to move the foo.eln file | ||
| 3969 | somewhere else and have the last Emacs instance using it delete it. | ||
| 3970 | To make it easy to find what files need to be removed we use two approaches. | ||
| 3971 | |||
| 3972 | In the 1) case we rename foo.eln to fooXXXXXX.eln.old in the same | ||
| 3973 | folder. When Emacs is unloading "foo" (either GC'd the native | ||
| 3974 | compilation unit or Emacs is closing (see below)) we delete all the | ||
| 3975 | .eln.old files in the folder where the original foo.eln was stored. | ||
| 3976 | |||
| 3977 | Ideally we'd figure out the new name of foo.eln and delete it if | ||
| 3978 | it ends in .eln.old. There is no simple API to do this in | ||
| 3979 | Windows. GetModuleFileName() returns the original filename, not the | ||
| 3980 | current one. This forces us to put .eln.old files in an agreed upon | ||
| 3981 | path. We cannot use %TEMP% because it may be in another drive and then | ||
| 3982 | the rename operation would fail. | ||
| 3983 | |||
| 3984 | In the 2) case we can't use the same folder where the .eln file | ||
| 3985 | resided, as we are trying to completely remove the package. Since we | ||
| 3986 | are removing packages we can safely move the .eln.old file to | ||
| 3987 | `package-user-dir' as we are sure that that would not mean changing | ||
| 3988 | drives. | ||
| 3989 | |||
| 3990 | * Implementation details | ||
| 3991 | |||
| 3992 | The concept of disposal of a native compilation unit refers to | ||
| 3993 | unloading the shared library and deleting all the .eln.old files in | ||
| 3994 | the directory. These are two separate steps. We'll call them | ||
| 3995 | early-disposal and late-disposal. | ||
| 3996 | |||
| 3997 | There are two data structures used: | ||
| 3998 | |||
| 3999 | - The `all_loaded_comp_units_h` hashtable. | ||
| 4000 | |||
| 4001 | This hashtable is used like an array of weak references to native | ||
| 4002 | compilation units. This hash table is filled by load_comp_unit() and | ||
| 4003 | dispose_all_remaining_comp_units() iterates over all values that were | ||
| 4004 | not disposed by the GC and performs all disposal steps when Emacs is | ||
| 4005 | closing. | ||
| 4006 | |||
| 4007 | - The `delayed_comp_unit_disposal_list` list. | ||
| 4008 | |||
| 4009 | This is were the dispose_comp_unit() function, when called by the GC | ||
| 4010 | sweep stage, stores the original filenames of the disposed native | ||
| 4011 | compilation units. This is an ad-hoc C structure instead of a Lisp | ||
| 4012 | cons because we need to allocate instances of this structure during | ||
| 4013 | the GC. | ||
| 4014 | |||
| 4015 | The finish_delayed_disposal_of_comp_units() function will iterate over | ||
| 4016 | this list and perform the late-disposal step when Emacs is closing. | ||
| 4017 | |||
| 4018 | */ | ||
| 4019 | |||
| 4020 | #ifdef WINDOWSNT | ||
| 4021 | #define OLD_ELN_SUFFIX_REGEXP build_string ("\\.eln\\.old\\'") | ||
| 4022 | |||
| 4023 | static Lisp_Object all_loaded_comp_units_h; | ||
| 4024 | |||
| 4025 | /* We need to allocate instances of this struct during a GC | ||
| 4026 | * sweep. This is why it can't be transformed into a simple cons. | ||
| 4027 | */ | ||
| 4028 | struct delayed_comp_unit_disposal | ||
| 4029 | { | ||
| 4030 | struct delayed_comp_unit_disposal *next; | ||
| 4031 | char *filename; | ||
| 4032 | }; | ||
| 4033 | |||
| 4034 | struct delayed_comp_unit_disposal *delayed_comp_unit_disposal_list; | ||
| 4035 | |||
| 4036 | static Lisp_Object | ||
| 4037 | return_nil (Lisp_Object arg) | ||
| 4038 | { | ||
| 4039 | return Qnil; | ||
| 4040 | } | ||
| 4041 | |||
| 4042 | /* Tries to remove all *.eln.old files in DIRNAME. | ||
| 4043 | |||
| 4044 | * Any error is ignored because it may be due to the file being loaded | ||
| 4045 | * in another Emacs instance. | ||
| 4046 | */ | ||
| 4047 | static void | ||
| 4048 | clean_comp_unit_directory (Lisp_Object dirpath) | ||
| 4049 | { | ||
| 4050 | if (NILP (dirpath)) | ||
| 4051 | return; | ||
| 4052 | Lisp_Object files_in_dir; | ||
| 4053 | files_in_dir = internal_condition_case_4 (Fdirectory_files, dirpath, Qt, | ||
| 4054 | OLD_ELN_SUFFIX_REGEXP, Qnil, Qt, | ||
| 4055 | return_nil); | ||
| 4056 | FOR_EACH_TAIL (files_in_dir) { DeleteFile (SSDATA (XCAR (files_in_dir))); } | ||
| 4057 | } | ||
| 4058 | |||
| 4059 | /* Tries to remove all *.eln.old files in `package-user-dir'. | ||
| 4060 | |||
| 4061 | * This is called when Emacs is closing to clean any *.eln left from a | ||
| 4062 | * deleted package. | ||
| 4063 | */ | ||
| 4064 | void | ||
| 4065 | clean_package_user_dir_of_old_comp_units (void) | ||
| 4066 | { | ||
| 4067 | Lisp_Object package_user_dir | ||
| 4068 | = find_symbol_value (intern ("package-user-dir")); | ||
| 4069 | if (EQ (package_user_dir, Qunbound) || !STRINGP (package_user_dir)) | ||
| 4070 | return; | ||
| 4071 | |||
| 4072 | clean_comp_unit_directory (package_user_dir); | ||
| 4073 | } | ||
| 4074 | |||
| 4075 | /* This function disposes all compilation units that are still loaded. | ||
| 4076 | * It is important that this function is called only right before | ||
| 4077 | * Emacs is closed, otherwise we risk running a subr that is | ||
| 4078 | * implemented in an unloaded dynamic library. | ||
| 4079 | */ | ||
| 4080 | void | ||
| 4081 | dispose_all_remaining_comp_units (void) | ||
| 4082 | { | ||
| 4083 | struct Lisp_Hash_Table *h = XHASH_TABLE (all_loaded_comp_units_h); | ||
| 4084 | |||
| 4085 | for (ptrdiff_t i = 0; i < HASH_TABLE_SIZE (h); ++i) | ||
| 4086 | { | ||
| 4087 | Lisp_Object k = HASH_KEY (h, i); | ||
| 4088 | if (!EQ (k, Qunbound)) | ||
| 4089 | { | ||
| 4090 | Lisp_Object val = HASH_VALUE (h, i); | ||
| 4091 | struct Lisp_Native_Comp_Unit *cu = XNATIVE_COMP_UNIT (val); | ||
| 4092 | dispose_comp_unit (cu, false); | ||
| 4093 | } | ||
| 4094 | } | ||
| 4095 | } | ||
| 4096 | |||
| 4097 | /* This function finishes the disposal of compilation units that were | ||
| 4098 | * passed to `dispose_comp_unit` with DELAY == true. | ||
| 4099 | * | ||
| 4100 | * This function is called when Emacs is idle and when it is about to | ||
| 4101 | * close. | ||
| 4102 | */ | ||
| 4103 | void | ||
| 4104 | finish_delayed_disposal_of_comp_units (void) | ||
| 4105 | { | ||
| 4106 | for (struct delayed_comp_unit_disposal *item | ||
| 4107 | = delayed_comp_unit_disposal_list; | ||
| 4108 | delayed_comp_unit_disposal_list; item = delayed_comp_unit_disposal_list) | ||
| 4109 | { | ||
| 4110 | delayed_comp_unit_disposal_list = item->next; | ||
| 4111 | Lisp_Object dirname = internal_condition_case_1 ( | ||
| 4112 | Ffile_name_directory, build_string (item->filename), Qt, return_nil); | ||
| 4113 | clean_comp_unit_directory (dirname); | ||
| 4114 | xfree (item->filename); | ||
| 4115 | xfree (item); | ||
| 4116 | } | ||
| 4117 | } | ||
| 4118 | #endif | ||
| 4119 | |||
| 4120 | /* This function puts the compilation unit in the | ||
| 4121 | * `all_loaded_comp_units_h` hashmap. | ||
| 4122 | */ | ||
| 4123 | static void | ||
| 4124 | register_native_comp_unit (Lisp_Object comp_u) | ||
| 4125 | { | ||
| 4126 | #ifdef WINDOWSNT | ||
| 4127 | Fputhash (CALL1I (gensym, Qnil), comp_u, all_loaded_comp_units_h); | ||
| 4128 | #endif | ||
| 4129 | } | ||
| 4130 | |||
| 4131 | /* This function disposes compilation units. It is called during the GC sweep | ||
| 4132 | * stage and when Emacs is closing. | ||
| 4133 | |||
| 4134 | * On Windows the the DELAY parameter specifies whether the native | ||
| 4135 | * compilation file will be deleted right away (if necessary) or put | ||
| 4136 | * on a list. That list will be dealt with by | ||
| 4137 | * `finish_delayed_disposal_of_comp_units`. | ||
| 4138 | */ | ||
| 4139 | void | ||
| 4140 | dispose_comp_unit (struct Lisp_Native_Comp_Unit *comp_handle, bool delay) | ||
| 4141 | { | ||
| 4142 | eassert (comp_handle->handle); | ||
| 4143 | dynlib_close (comp_handle->handle); | ||
| 4144 | #ifdef WINDOWSNT | ||
| 4145 | if (!delay) | ||
| 4146 | { | ||
| 4147 | Lisp_Object dirname = internal_condition_case_1 ( | ||
| 4148 | Ffile_name_directory, build_string (comp_handle->cfile), Qt, | ||
| 4149 | return_nil); | ||
| 4150 | if (!NILP (dirname)) | ||
| 4151 | clean_comp_unit_directory (dirname); | ||
| 4152 | xfree (comp_handle->cfile); | ||
| 4153 | comp_handle->cfile = NULL; | ||
| 4154 | } | ||
| 4155 | else | ||
| 4156 | { | ||
| 4157 | struct delayed_comp_unit_disposal *head; | ||
| 4158 | head = xmalloc (sizeof (struct delayed_comp_unit_disposal)); | ||
| 4159 | head->next = delayed_comp_unit_disposal_list; | ||
| 4160 | head->filename = comp_handle->cfile; | ||
| 4161 | comp_handle->cfile = NULL; | ||
| 4162 | delayed_comp_unit_disposal_list = head; | ||
| 4163 | } | ||
| 4164 | #endif | ||
| 4165 | } | ||
| 4166 | |||
| 4167 | |||
| 3942 | /***********************************/ | 4168 | /***********************************/ |
| 3943 | /* Deferred compilation mechanism. */ | 4169 | /* Deferred compilation mechanism. */ |
| 3944 | /***********************************/ | 4170 | /***********************************/ |
| @@ -4159,6 +4385,12 @@ load_comp_unit (struct Lisp_Native_Comp_Unit *comp_u, bool loading_dump, | |||
| 4159 | d_vec_len = XFIXNUM (Flength (comp_u->data_impure_vec)); | 4385 | d_vec_len = XFIXNUM (Flength (comp_u->data_impure_vec)); |
| 4160 | for (EMACS_INT i = 0; i < d_vec_len; i++) | 4386 | for (EMACS_INT i = 0; i < d_vec_len; i++) |
| 4161 | data_imp_relocs[i] = AREF (comp_u->data_impure_vec, i); | 4387 | data_imp_relocs[i] = AREF (comp_u->data_impure_vec, i); |
| 4388 | |||
| 4389 | /* If we register them while dumping we will get some entries in | ||
| 4390 | the hash table that will be duplicated when pdumper calls | ||
| 4391 | load_comp_unit. */ | ||
| 4392 | if (!will_dump_p ()) | ||
| 4393 | register_native_comp_unit (comp_u_lisp_obj); | ||
| 4162 | } | 4394 | } |
| 4163 | 4395 | ||
| 4164 | if (!loading_dump) | 4396 | if (!loading_dump) |
| @@ -4316,6 +4548,9 @@ DEFUN ("native-elisp-load", Fnative_elisp_load, Snative_elisp_load, 1, 2, 0, | |||
| 4316 | if (!comp_u->handle) | 4548 | if (!comp_u->handle) |
| 4317 | xsignal2 (Qnative_lisp_load_failed, file, build_string (dynlib_error ())); | 4549 | xsignal2 (Qnative_lisp_load_failed, file, build_string (dynlib_error ())); |
| 4318 | comp_u->file = file; | 4550 | comp_u->file = file; |
| 4551 | #ifdef WINDOWSNT | ||
| 4552 | comp_u->cfile = xlispstrdup (file); | ||
| 4553 | #endif | ||
| 4319 | comp_u->data_vec = Qnil; | 4554 | comp_u->data_vec = Qnil; |
| 4320 | comp_u->lambda_gc_guard = CALLN (Fmake_hash_table, QCtest, Qeq); | 4555 | comp_u->lambda_gc_guard = CALLN (Fmake_hash_table, QCtest, Qeq); |
| 4321 | comp_u->lambda_c_name_idx_h = CALLN (Fmake_hash_table, QCtest, Qequal); | 4556 | comp_u->lambda_c_name_idx_h = CALLN (Fmake_hash_table, QCtest, Qequal); |
| @@ -4464,6 +4699,11 @@ syms_of_comp (void) | |||
| 4464 | staticpro (&delayed_sources); | 4699 | staticpro (&delayed_sources); |
| 4465 | delayed_sources = Qnil; | 4700 | delayed_sources = Qnil; |
| 4466 | 4701 | ||
| 4702 | #ifdef WINDOWSNT | ||
| 4703 | staticpro (&all_loaded_comp_units_h); | ||
| 4704 | all_loaded_comp_units_h = CALLN(Fmake_hash_table, QCweakness, Qvalue); | ||
| 4705 | #endif | ||
| 4706 | |||
| 4467 | DEFVAR_LISP ("comp-ctxt", Vcomp_ctxt, | 4707 | DEFVAR_LISP ("comp-ctxt", Vcomp_ctxt, |
| 4468 | doc: /* The compiler context. */); | 4708 | doc: /* The compiler context. */); |
| 4469 | Vcomp_ctxt = Qnil; | 4709 | Vcomp_ctxt = Qnil; |