emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] feature/tramp-thread-safe bba62df 1/7: Introducing thread-


From: Michael Albinus
Subject: [Emacs-diffs] feature/tramp-thread-safe bba62df 1/7: Introducing thread-safe Tramp
Date: Sat, 4 Aug 2018 06:50:10 -0400 (EDT)

branch: feature/tramp-thread-safe
commit bba62df63391949f483f1a531667a5c767dbc2a1
Author: Michael Albinus <address@hidden>
Commit: Michael Albinus <address@hidden>

    Introducing thread-safe Tramp
    
    * doc/misc/tramp.texi (History): Tramp is thread-safe now.
    
    * etc/NEWS: Mention asynchronous visiting of files, and Tramp being
    thread-safe.
    
    * lisp/files.el (find-file-read-args): Add optional arument WILDCARDS.
    (find-file-with-threads): New defmacro.
    (find-file, find-file-other-window, find-file-other-frame)
    (find-file-existing, find-alternate-file-other-window)
    (find-alternate-file, find-file-literally): New optional argument
    THREADS.  Adapt docstring if necessary.  Wrap the body by
    `find-file-with-threads'.
    (find-file--read-only, find-file-read-only)
    (find-file-read-only-other-window)
    (find-file-read-only-other-frame): New optional argument THREADS.
    (find-file-noselect): New optional argument THREADS.  Adapt
    docstring.  Create new threads for all files detected through WILDCARDS.
    
    * lisp/net/tramp-compat.el (tramp-compat-main-thread): New defconst.
    (tramp-compat-current-thread, tramp-compat-thread-yield)
    (tramp-compat-signal): New defsubst.
    (tramp-compat-with-mutex): New defmacro.
    
    * lisp/net/tramp.el (tramp-debug-outline-regexp): Add thread regexp.
    (tramp-debug-font-lock-keywords): New defconst.
    (tramp-debug-outline-level): Adapt to changed
    `tramp-debug-outline-regexp'.
    (tramp-get-debug-buffer): Use `tramp-debug-font-lock-keywords'.
    (tramp-debug-message): Insert also thread name.
    (tramp-error): Propagate error to main thread.
    (tramp-condition-case-unless-debug): Add declarations.
    (tramp-mutex): New defvar.
    (tramp-get-mutex): New defun.
    (tramp-file-name-handler): Adapt to threads.  Lock Tramp mutex.
    Set process thread.  Propagate signals to main thread.
    
    * lisp/vc/vc-hooks.el (vc-mutex): New autoloaded variable.
    (vc-refresh-state): Let it run in own thread.  Lock it with `vc-mutex'.
    
    * test/lisp/net/tramp-archive-tests.el (tramp-archive-test44-auto-load)
    (tramp-archive-test44-delay-load): Rename.
    
    * test/lisp/net/tramp-tests.el (tramp-test43-threads): New test.
    (tramp-test44-auto-load, tramp-test44-delay-load)
    (tramp-test44-recursive-load, tramp-test44-remote-load-path)
    (tramp-test45-unload): Rename.
---
 doc/misc/tramp.texi                  |   2 +-
 etc/NEWS                             |  11 ++
 lisp/files.el                        | 194 +++++++++++++++++++---------
 lisp/net/tramp-compat.el             |  32 +++++
 lisp/net/tramp.el                    | 243 ++++++++++++++++++++---------------
 lisp/vc/vc-hooks.el                  | 125 ++++++++++--------
 test/lisp/net/tramp-archive-tests.el |   4 +-
 test/lisp/net/tramp-tests.el         | 109 +++++++++++++++-
 8 files changed, 497 insertions(+), 223 deletions(-)

diff --git a/doc/misc/tramp.texi b/doc/misc/tramp.texi
index a96f4dd..04002b7 100644
--- a/doc/misc/tramp.texi
+++ b/doc/misc/tramp.texi
@@ -407,7 +407,7 @@ started in February 2009.  Remote commands on MS Windows 
hosts since
 September 2011.  Ad-hoc multi-hop methods (with a changed syntax)
 re-enabled in November 2011.  In November 2012, added Juergen
 Hoetzel's @file{tramp-adb.el}.  Archive file names are supported since
-December 2017.
+December 2017.  @value{tramp} has been made thread-safe in July 2018.
 
 XEmacs support was stopped in January 2016.  Since March 2017,
 @value{tramp} syntax mandates a method.
diff --git a/etc/NEWS b/etc/NEWS
index fc2a5d4..50d1b59 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -209,6 +209,14 @@ large files.  Now it also offers a third alternative: to 
visit the
 file literally, as in 'find-file-literally', which speeds up
 navigation and editing of large files.
 
+--- (Documentation to be added)
+** Files can be visited asynchronously.
+If the file visiting commands are prefixed like 'C-u C-x C-f ...', the
+files are loaded asynchronously into the respective buffers.  I.e.,
+Emacs is still responsive while loading the files, which is useful
+especially for remote files.  See the node "(emacs) Visiting" in the
+user manual for the supported commands..
+
 
 * Changes in Specialized Modes and Packages in Emacs 27.1
 
@@ -545,6 +553,9 @@ process.  It now accepts signals specified either by name 
or by its number.
 
 ** Tramp
 
+---
+*** Tramp is now thread-safe.
+
 +++
 *** New connection method "owncloud", which allows to access OwnCloud
 or NextCloud hosted files and directories.
diff --git a/lisp/files.el b/lisp/files.el
index eabb3c0..ddeb92f 100644
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -1576,11 +1576,25 @@ rather than FUN itself, to `minibuffer-setup-hook'."
              ,@body)
          (remove-hook 'minibuffer-setup-hook ,hook)))))
 
-(defun find-file-read-args (prompt mustmatch)
-  (list (read-file-name prompt nil default-directory mustmatch)
-       t))
+(defun find-file-read-args (prompt mustmatch &optional wildcards)
+  "Return the interactive spec (<filename> <threads>).
+If WILDCARDS is non-nil. return the spec (<filename> t <threads>)."
+  (let ((filename (read-file-name prompt nil default-directory mustmatch))
+        (threads (and current-prefix-arg (featurep 'threads))))
+    (if wildcards `(,filename t ,threads) `(,filename ,threads))))
+
+(defmacro find-file-with-threads (filename threads &rest body)
+  "Run BODY in an own thread, if THREADS is non-nil."
+  (declare (indent 2) (debug t))
+  `(if ,threads
+       (progn
+         (make-thread
+          (lambda () (with-mutex vc-mutex ,@body))
+          (concat "find-file " ,filename))
+         (thread-yield))
+     ,@body))
 
-(defun find-file (filename &optional wildcards)
+(defun find-file (filename &optional wildcards threads)
   "Edit file FILENAME.
 Switch to a buffer visiting file FILENAME,
 creating one if none already exists.
@@ -1607,17 +1621,22 @@ Interactively, or if WILDCARDS is non-nil in a call 
from Lisp,
 expand wildcards (if any) and visit multiple files.  You can
 suppress wildcard expansion by setting `find-file-wildcards' to nil.
 
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument.
+
 To visit a file without any kind of conversion and without
 automatically choosing a major mode, use \\[find-file-literally]."
   (interactive
    (find-file-read-args "Find file: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (let ((value (find-file-noselect filename nil nil wildcards)))
-    (if (listp value)
-       (mapcar 'pop-to-buffer-same-window (nreverse value))
-      (pop-to-buffer-same-window value))))
-
-(defun find-file-other-window (filename &optional wildcards)
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file-with-threads filename threads
+    (let ((value (find-file-noselect filename nil nil wildcards threads)))
+      (if (listp value)
+          (mapcar 'pop-to-buffer-same-window (nreverse value))
+        (pop-to-buffer-same-window value)))))
+
+(defun find-file-other-window (filename &optional wildcards threads)
   "Edit file FILENAME, in another window.
 
 Like \\[find-file] (which see), but creates a new window or reuses
@@ -1636,20 +1655,25 @@ current directory to be available on first 
\\[next-history-element]
 request.
 
 Interactively, or if WILDCARDS is non-nil in a call from Lisp,
-expand wildcards (if any) and visit multiple files."
+expand wildcards (if any) and visit multiple files.
+
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument."
   (interactive
    (find-file-read-args "Find file in other window: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (let ((value (find-file-noselect filename nil nil wildcards)))
-    (if (listp value)
-       (progn
-         (setq value (nreverse value))
-         (switch-to-buffer-other-window (car value))
-         (mapc 'switch-to-buffer (cdr value))
-         value)
-      (switch-to-buffer-other-window value))))
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file-with-threads filename threads
+    (let ((value (find-file-noselect filename nil nil wildcards threads)))
+      (if (listp value)
+         (progn
+           (setq value (nreverse value))
+           (switch-to-buffer-other-window (car value))
+           (mapc 'switch-to-buffer (cdr value))
+           value)
+        (switch-to-buffer-other-window value)))))
 
-(defun find-file-other-frame (filename &optional wildcards)
+(defun find-file-other-frame (filename &optional wildcards threads)
   "Edit file FILENAME, in another frame.
 
 Like \\[find-file] (which see), but creates a new frame or reuses
@@ -1668,76 +1692,90 @@ current directory to be available on first 
\\[next-history-element]
 request.
 
 Interactively, or if WILDCARDS is non-nil in a call from Lisp,
-expand wildcards (if any) and visit multiple files."
+expand wildcards (if any) and visit multiple files.
+
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument."
   (interactive
    (find-file-read-args "Find file in other frame: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (let ((value (find-file-noselect filename nil nil wildcards)))
-    (if (listp value)
-       (progn
-         (setq value (nreverse value))
-         (switch-to-buffer-other-frame (car value))
-         (mapc 'switch-to-buffer (cdr value))
-         value)
-      (switch-to-buffer-other-frame value))))
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file-with-threads filename threads
+    (let ((value (find-file-noselect filename nil nil wildcards threads)))
+      (if (listp value)
+         (progn
+           (setq value (nreverse value))
+           (switch-to-buffer-other-frame (car value))
+           (mapc 'switch-to-buffer (cdr value))
+           value)
+        (switch-to-buffer-other-frame value)))))
 
-(defun find-file-existing (filename)
+(defun find-file-existing (filename &optional threads)
    "Edit the existing file FILENAME.
 Like \\[find-file], but only allow a file that exists, and do not allow
-file names with wildcards."
-   (interactive (nbutlast (find-file-read-args "Find existing file: " t)))
+file names with wildcards.
+
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument."
+   (interactive
+    (find-file-read-args "Find existing file: " t))
    (if (and (not (called-interactively-p 'interactive))
            (not (file-exists-p filename)))
        (error "%s does not exist" filename)
-     (find-file filename)
+     (find-file filename nil threads)
      (current-buffer)))
 
-(defun find-file--read-only (fun filename wildcards)
+(defun find-file--read-only (fun filename wildcards threads)
   (unless (or (and wildcards find-file-wildcards
                   (not (file-name-quoted-p filename))
                   (string-match "[[*?]" filename))
              (file-exists-p filename))
     (error "%s does not exist" filename))
-  (let ((value (funcall fun filename wildcards)))
+  (let ((value (funcall fun filename wildcards threads)))
     (mapc (lambda (b) (with-current-buffer b (read-only-mode 1)))
          (if (listp value) value (list value)))
     value))
 
-(defun find-file-read-only (filename &optional wildcards)
+(defun find-file-read-only (filename &optional wildcards threads)
   "Edit file FILENAME but don't allow changes.
 Like \\[find-file], but marks buffer as read-only.
 Use \\[read-only-mode] to permit editing."
   (interactive
    (find-file-read-args "Find file read-only: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (find-file--read-only #'find-file filename wildcards))
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file--read-only #'find-file filename wildcards threads))
 
-(defun find-file-read-only-other-window (filename &optional wildcards)
+(defun find-file-read-only-other-window (filename &optional wildcards threads)
   "Edit file FILENAME in another window but don't allow changes.
 Like \\[find-file-other-window], but marks buffer as read-only.
 Use \\[read-only-mode] to permit editing."
   (interactive
    (find-file-read-args "Find file read-only other window: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (find-file--read-only #'find-file-other-window filename wildcards))
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file--read-only #'find-file-other-window filename wildcards threads))
 
-(defun find-file-read-only-other-frame (filename &optional wildcards)
+(defun find-file-read-only-other-frame (filename &optional wildcards threads)
   "Edit file FILENAME in another frame but don't allow changes.
 Like \\[find-file-other-frame], but marks buffer as read-only.
 Use \\[read-only-mode] to permit editing."
   (interactive
    (find-file-read-args "Find file read-only other frame: "
-                        (confirm-nonexistent-file-or-buffer)))
-  (find-file--read-only #'find-file-other-frame filename wildcards))
+                        (confirm-nonexistent-file-or-buffer) t))
+  (find-file--read-only #'find-file-other-frame filename wildcards threads))
 
-(defun find-alternate-file-other-window (filename &optional wildcards)
+(defun find-alternate-file-other-window (filename &optional wildcards threads)
   "Find file FILENAME as a replacement for the file in the next window.
 This command does not select that window.
 
 See \\[find-file] for the possible forms of the FILENAME argument.
 
 Interactively, or if WILDCARDS is non-nil in a call from Lisp,
-expand wildcards (if any) and replace the file with multiple files."
+expand wildcards (if any) and replace the file with multiple files.
+
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument."
   (interactive
    (save-selected-window
      (other-window 1)
@@ -1750,12 +1788,12 @@ expand wildcards (if any) and replace the file with 
multiple files."
        (list (read-file-name
              "Find alternate file: " file-dir nil
               (confirm-nonexistent-file-or-buffer) file-name)
-            t))))
+            t (and current-prefix-arg (featurep 'threads))))))
   (if (one-window-p)
-      (find-file-other-window filename wildcards)
+      (find-file-other-window filename wildcards threads)
     (save-selected-window
       (other-window 1)
-      (find-alternate-file filename wildcards))))
+      (find-alternate-file filename wildcards threads))))
 
 ;; Defined and used in buffer.c, but not as a DEFVAR_LISP.
 (defvar kill-buffer-hook nil
@@ -1766,7 +1804,7 @@ See `kill-buffer'.
 Note: Be careful with let-binding this hook considering it is
 frequently used for cleanup.")
 
-(defun find-alternate-file (filename &optional wildcards)
+(defun find-alternate-file (filename &optional wildcards threads)
   "Find file FILENAME, select its buffer, kill previous buffer.
 If the current buffer now contains an empty file that you just visited
 \(presumably by mistake), use this command to visit the file you really want.
@@ -1776,6 +1814,10 @@ See \\[find-file] for the possible forms of the FILENAME 
argument.
 Interactively, or if WILDCARDS is non-nil in a call from Lisp,
 expand wildcards (if any) and replace the file with multiple files.
 
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument.
+
 If the current buffer is an indirect buffer, or the base buffer
 for one or more indirect buffers, the other buffer(s) are not
 killed."
@@ -1789,7 +1831,7 @@ killed."
      (list (read-file-name
            "Find alternate file: " file-dir nil
             (confirm-nonexistent-file-or-buffer) file-name)
-          t)))
+          t (and current-prefix-arg (featurep 'threads)))))
   (unless (run-hook-with-args-until-failure 'kill-buffer-query-functions)
     (user-error "Aborted"))
   (and (buffer-modified-p) buffer-file-name
@@ -1830,7 +1872,8 @@ killed."
           ;; Don't use `find-file' because it may end up using another window
           ;; in some corner cases, e.g. when the selected window is
           ;; softly-dedicated.
-         (let ((newbuf (find-file-noselect filename wildcards)))
+         (let ((newbuf
+                 (find-file-noselect filename nil nil wildcards threads)))
             (switch-to-buffer newbuf)))
       (when (eq obuf (current-buffer))
        ;; This executes if find-file gets an error
@@ -2102,7 +2145,7 @@ displayed on the minibuffer."
   (apply #'message format args)
   (when save-silently (message nil)))
 
-(defun find-file-noselect (filename &optional nowarn rawfile wildcards)
+(defun find-file-noselect (filename &optional nowarn rawfile wildcards threads)
   "Read file FILENAME into a buffer and return the buffer.
 If a buffer exists visiting FILENAME, return that one, but
 verify that the file has not changed since visited or saved.
@@ -2112,7 +2155,11 @@ Optional third arg RAWFILE non-nil means the file is 
read literally.
 Optional fourth arg WILDCARDS non-nil means do wildcard processing
 and visit all the matching files.  When wildcards are actually
 used and expanded, return a list of buffers that are visiting
-the various files."
+the various files.
+
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  When several files are loaded due to WILDCARDS,
+every file will be loaded in an own thread."
   (setq filename
        (abbreviate-file-name
         (expand-file-name filename)))
@@ -2134,7 +2181,24 @@ the various files."
              (find-file-wildcards nil))
          (if (null files)
              (find-file-noselect filename)
-           (mapcar #'find-file-noselect files)))
+
+            (if threads
+                (let (result)
+                  ;; Create one thread per file.
+                  (setq threads
+                        (mapcar
+                         (lambda (file)
+                           (make-thread
+                            (lambda () (find-file-noselect file))
+                            (concat "find-file-noselect " file)))
+                         files))
+                  ;; Collect the results.
+                  (thread-yield)
+                  (dolist (thread threads result)
+                    (setq result (cons (thread-join thread) result))))
+
+              (mapcar #'find-file-noselect files))))
+
       (let* ((buf (get-file-buffer filename))
             (truename (abbreviate-file-name (file-truename filename)))
             (attributes (file-attributes truename))
@@ -2397,7 +2461,7 @@ This has the `permanent-local' property, which takes 
effect if you
 make the variable buffer-local.")
 (put 'find-file-literally 'permanent-local t)
 
-(defun find-file-literally (filename)
+(defun find-file-literally (filename &optional threads)
   "Visit file FILENAME with no conversion of any kind.
 Format conversion and character code conversion are both disabled,
 and multibyte characters are disabled in the resulting buffer.
@@ -2409,6 +2473,10 @@ file due to `require-final-newline' is also disabled.
 If Emacs already has a buffer which is visiting the file,
 this command asks you whether to visit it literally instead.
 
+If THREADS is non-nil, the file will be loaded into the buffer
+asynchronously.  Interactively, this is indicated by a prefix
+argument.
+
 In non-interactive use, the value is the buffer where the file is
 visited literally.  If the file was visited in a buffer before
 this command was invoked, it will reuse the existing buffer,
@@ -2420,10 +2488,10 @@ In a Lisp program, if you want to be sure of accessing 
a file's
 contents literally, you should create a temporary buffer and then read
 the file contents into it using `insert-file-contents-literally'."
   (interactive
-   (list (read-file-name
-         "Find file literally: " nil default-directory
-         (confirm-nonexistent-file-or-buffer))))
-  (switch-to-buffer (find-file-noselect filename nil t)))
+   (find-file-read-args "Find file literally: "
+                        (confirm-nonexistent-file-or-buffer)))
+  (find-file-with-threads filename threads
+    (switch-to-buffer (find-file-noselect filename nil t nil threads))))
 
 (defun after-find-file (&optional error warn noauto
                                  _after-find-file-from-revert-buffer
diff --git a/lisp/net/tramp-compat.el b/lisp/net/tramp-compat.el
index 9af57fb..bb6fc18 100644
--- a/lisp/net/tramp-compat.el
+++ b/lisp/net/tramp-compat.el
@@ -236,6 +236,38 @@ If NAME is a remote file name, the local part of NAME is 
unquoted."
 (defconst tramp-compat-use-url-tramp-p (fboundp 'temporary-file-directory)
   "Whether to use url-tramp.el.")
 
+;; Threads have entered Emacs 26.1, `main-thread' in Emacs 27.1.  But
+;; then, they might not exist when Emacs is configured
+;; --without-threads.
+(defconst tramp-compat-main-thread (bound-and-true-p main-thread)
+  "The main thread of Emacs, if compiled --with-threads.")
+
+(defsubst tramp-compat-current-thread ()
+  "The current thread, or nil if compiled --without-threads."
+  (tramp-compat-funcall 'current-thread))
+
+(defsubst tramp-compat-thread-yield ()
+  "Yield the CPU to another thread."
+  (tramp-compat-funcall 'thread-yield))
+
+(defsubst tramp-compat-signal (error-symbol data)
+  "Signal an error to the main thread."
+  (when tramp-compat-main-thread
+    (tramp-compat-funcall
+     'thread-signal tramp-compat-main-thread error-symbol data))
+  (signal error-symbol data))
+
+;; Mutexes have entered Emacs 26.1.  Once we use only Emacs 26+, we
+;; must check (mutexp mutex), because the other functions might still
+;; not exist when Emacs is configured --without-threads.
+(defmacro tramp-compat-with-mutex (mutex &rest body)
+  "Invoke BODY with MUTEX held, releasing MUTEX when done.
+This is the simplest safe way to acquire and release a mutex."
+  (declare (indent 1) (debug t))
+  `(if (fboundp 'with-mutex)
+       (with-mutex ,mutex ,@body)
+     ,@body))
+
 ;; `exec-path' is new in Emacs 27.1.
 (eval-and-compile
   (if (fboundp 'exec-path)
diff --git a/lisp/net/tramp.el b/lisp/net/tramp.el
index d56b09a..48a45d7 100644
--- a/lisp/net/tramp.el
+++ b/lisp/net/tramp.el
@@ -1521,7 +1521,17 @@ version, the function does nothing."
       (format "*debug tramp/%s %s*" method host-port))))
 
 (defconst tramp-debug-outline-regexp
-  "[0-9]+:[0-9]+:[0-9]+\\.[0-9]+ [a-z0-9-]+ (\\([0-9]+\\)) #"
+  (concat
+   "[0-9]+:[0-9]+:[0-9]+\\.[0-9]+ " ;; Timestamp.
+   "\\(?:\\(#<thread .+>\\) \\)?"   ;; Thread.
+   "[a-z0-9-]+ (\\([0-9]+\\)) #")   ;; Function name, verbosity.
+  "Used for highlighting Tramp debug buffers in `outline-mode'.")
+
+(defconst tramp-debug-font-lock-keywords
+  '(list
+    (concat "^\\(?:" tramp-debug-outline-regexp "\\).+")
+    '(1 font-lock-warning-face t t)
+    '(0 (outline-font-lock-face) keep t))
   "Used for highlighting Tramp debug buffers in `outline-mode'.")
 
 (defun tramp-debug-outline-level ()
@@ -1529,7 +1539,7 @@ version, the function does nothing."
 Point must be at the beginning of a header line.
 
 The outline level is equal to the verbosity of the Tramp message."
-  (1+ (string-to-number (match-string 1))))
+  (1+ (string-to-number (match-string 2))))
 
 (defun tramp-get-debug-buffer (vec)
   "Get the debug buffer for VEC."
@@ -1542,13 +1552,13 @@ The outline level is equal to the verbosity of the 
Tramp message."
       ;; Activate `outline-mode'.  This runs `text-mode-hook' and
       ;; `outline-mode-hook'.  We must prevent that local processes
       ;; die.  Yes: I've seen `flyspell-mode', which starts "ispell".
-      ;; Furthermore, `outline-regexp' must have the correct value
-      ;; already, because it is used by `font-lock-compile-keywords'.
-      (let ((default-directory (tramp-compat-temporary-file-directory))
-           (outline-regexp tramp-debug-outline-regexp))
+      (let ((default-directory (tramp-compat-temporary-file-directory)))
        (outline-mode))
-      (set (make-local-variable 'outline-regexp) tramp-debug-outline-regexp)
       (set (make-local-variable 'outline-level) 'tramp-debug-outline-level)
+      (set (make-local-variable 'font-lock-keywords)
+          `(t
+            (eval ,tramp-debug-font-lock-keywords)
+            ,(eval tramp-debug-font-lock-keywords)))
       ;; Do not edit the debug buffer.
       (set-keymap-parent (current-local-map) special-mode-map))
     (current-buffer)))
@@ -1576,6 +1586,9 @@ ARGUMENTS to actually emit the message (if applicable)."
     (let ((now (current-time)))
       (insert (format-time-string "%T." now))
       (insert (format "%06d " (nth 2 now))))
+    ;; Threads.
+    (unless (eq (tramp-compat-current-thread) tramp-compat-main-thread)
+      (insert (format "%s " (tramp-compat-current-thread))))
     ;; Calling Tramp function.  We suppress compat and trace functions
     ;; from being displayed.
     (let ((btn 1) btf fn)
@@ -1688,7 +1701,7 @@ function is meant for debugging purposes."
   "Emit an error.
 VEC-OR-PROC identifies the connection to use, SIGNAL is the
 signal identifier to be raised, remaining arguments passed to
-`tramp-message'.  Finally, signal SIGNAL is raised."
+`tramp-message'.  Finally, signal SIGNAL is raised to the main thread."
   (let (tramp-message-show-message)
     (tramp-backtrace vec-or-proc)
     (unless arguments
@@ -1704,7 +1717,8 @@ signal identifier to be raised, remaining arguments 
passed to
        (list signal
              (get signal 'error-message)
              (apply #'format-message fmt-string arguments)))))
-    (signal signal (list (apply #'format-message fmt-string arguments)))))
+    (tramp-compat-signal
+     signal (list (apply #'format-message fmt-string arguments)))))
 
 (defsubst tramp-error-with-buffer
   (buf vec-or-proc signal fmt-string &rest arguments)
@@ -2197,6 +2211,7 @@ ARGS are the arguments OPERATION has been called with."
 (defmacro tramp-condition-case-unless-debug
   (var bodyform &rest handlers)
   "Like `condition-case-unless-debug' but `tramp-debug-on-error'."
+  (declare (indent 1) (debug t))
   `(let ((debug-on-error tramp-debug-on-error))
      (condition-case-unless-debug ,var ,bodyform ,@handlers)))
 
@@ -2227,108 +2242,134 @@ preventing reentrant calls of Tramp.")
 Together with `tramp-locked', this implements a locking mechanism
 preventing reentrant calls of Tramp.")
 
+;; Mutexes have entered Emacs 26.1.
+(defvar tramp-mutex (tramp-compat-funcall 'make-mutex "tramp")
+  "Global mutex for Tramp threads.")
+
+(defun tramp-get-mutex (vec)
+  "Return the mutex locking Tramp threads for VEC."
+  (let ((p (tramp-get-connection-process vec)))
+    (if p
+      (with-tramp-connection-property p "mutex"
+       (tramp-compat-funcall 'make-mutex (process-name p)))
+      tramp-mutex)))
+
 ;; Main function.
 (defun tramp-file-name-handler (operation &rest args)
   "Invoke Tramp file name handler.
-Falls back to normal file name handler if no Tramp file name handler exists."
+Falls back to normal file name handler if no Tramp file name handler exists.
+If Emacs is compiled --with-threads, the body is protected by a mutex."
   (let ((filename (apply 'tramp-file-name-for-operation operation args)))
     (if (tramp-tramp-file-p filename)
        (save-match-data
           (setq filename (tramp-replace-environment-variables filename))
           (with-parsed-tramp-file-name filename nil
-            (let ((completion (tramp-completion-mode-p))
-                 (foreign
-                  (tramp-find-foreign-file-name-handler filename operation))
-                 result)
-             ;; Call the backend function.
-             (if foreign
-                 (tramp-condition-case-unless-debug err
-                   (let ((sf (symbol-function foreign)))
-                     ;; Some packages set the default directory to a
-                     ;; remote path, before respective Tramp packages
-                     ;; are already loaded.  This results in
-                     ;; recursive loading.  Therefore, we load the
-                     ;; Tramp packages locally.
-                     (when (autoloadp sf)
-                       (let ((default-directory
-                               (tramp-compat-temporary-file-directory)))
-                         (load (cadr sf) 'noerror 'nomessage)))
-;;                   (tramp-message
-;;                    v 4 "Running `%s'..." (cons operation args))
-                     ;; If `non-essential' is non-nil, Tramp shall
-                     ;; not open a new connection.
-                     ;; If Tramp detects that it shouldn't continue
-                     ;; to work, it throws the `suppress' event.
-                     ;; This could happen for example, when Tramp
-                     ;; tries to open the same connection twice in a
-                     ;; short time frame.
-                     ;; In both cases, we try the default handler then.
-                     (setq result
-                           (catch 'non-essential
-                             (catch 'suppress
-                               (when (and tramp-locked (not tramp-locker))
-                                 (setq tramp-locked nil)
-                                 (tramp-error
-                                  (car-safe tramp-current-connection)
-                                  'file-error
-                                  "Forbidden reentrant call of Tramp"))
-                               (let ((tl tramp-locked))
-                                 (setq tramp-locked t)
-                                 (unwind-protect
-                                     (let ((tramp-locker t))
-                                       (apply foreign operation args))
-                                   (setq tramp-locked tl))))))
-;;                   (tramp-message
-;;                    v 4 "Running `%s'...`%s'" (cons operation args) result)
-                     (cond
-                      ((eq result 'non-essential)
-                       (tramp-message
-                        v 5 "Non-essential received in operation %s"
-                        (cons operation args))
-                       (tramp-run-real-handler operation args))
-                      ((eq result 'suppress)
-                       (let (tramp-message-show-message)
+           ;; Give other threads a chance.
+           (tramp-compat-thread-yield)
+           ;; The mutex allows concurrent run of operations.  It
+           ;; guarantees, that the threads are not mixed.
+           (tramp-compat-with-mutex (tramp-get-mutex v)
+             (let ((completion (tramp-completion-mode-p))
+                   (foreign
+                    (tramp-find-foreign-file-name-handler filename operation))
+                   result)
+               ;; Call the backend function.
+               (if foreign
+                   (tramp-condition-case-unless-debug err
+                     (let ((sf (symbol-function foreign))
+                           p)
+                       ;; Some packages set the default directory to
+                       ;; a remote path, before respective Tramp
+                       ;; packages are already loaded.  This results
+                       ;; in recursive loading.  Therefore, we load
+                       ;; the Tramp packages locally.
+                       (when (autoloadp sf)
+                         (let ((default-directory
+                                 (tramp-compat-temporary-file-directory)))
+                           (load (cadr sf) 'noerror 'nomessage)))
+                       ;; (tramp-message
+                       ;;  v 4 "Running `%s'..." (cons operation args))
+                       ;; Switch process thread.
+                       (when (and tramp-mutex
+                                  (setq p (tramp-get-connection-process v)))
+                         (tramp-compat-funcall
+                          'set-process-thread p (tramp-compat-current-thread)))
+                       ;; If `non-essential' is non-nil, Tramp shall
+                       ;; not open a new connection.
+                       ;; If Tramp detects that it shouldn't continue
+                       ;; to work, it throws the `suppress' event.
+                       ;; This could happen for example, when Tramp
+                       ;; tries to open the same connection twice in
+                       ;; a short time frame.
+                       ;; In both cases, we try the default handler
+                       ;; then.
+                       (setq result
+                             (catch 'non-essential
+                               (catch 'suppress
+                                 (when (and tramp-locked (not tramp-locker))
+                                   (setq tramp-locked nil)
+                                   (tramp-error
+                                    (car-safe tramp-current-connection)
+                                    'file-error
+                                    "Forbidden reentrant call of Tramp"))
+                                 (let ((tl tramp-locked))
+                                   (setq tramp-locked t)
+                                   (unwind-protect
+                                       (let ((tramp-locker t))
+                                         (apply foreign operation args))
+                                     (setq tramp-locked tl))))))
+                       ;; (tramp-message
+                       ;;  v 4 "Running `%s'...`%s'" (cons operation args) 
result)
+                       (cond
+                        ((eq result 'non-essential)
                          (tramp-message
-                          v 1 "Suppress received in operation %s"
+                          v 5 "Non-essential received in operation %s"
                           (cons operation args))
-                         (tramp-cleanup-connection v t)
-                         (tramp-run-real-handler operation args)))
-                      (t result)))
-
-                   ;; Trace that somebody has interrupted the operation.
-                   ((debug quit)
-                    (let (tramp-message-show-message)
-                      (tramp-message
-                       v 1 "Interrupt received in operation %s"
-                       (cons operation args)))
-                    ;; Propagate the quit signal.
-                    (signal (car err) (cdr err)))
-
-                   ;; When we are in completion mode, some failed
-                   ;; operations shall return at least a default
-                   ;; value in order to give the user a chance to
-                   ;; correct the file name in the minibuffer.
-                   ;; In order to get a full backtrace, one could apply
-                   ;;   (setq tramp-debug-on-error t)
-                   (error
-                    (cond
-                     ((and completion (zerop (length localname))
-                           (memq operation '(file-exists-p file-directory-p)))
-                      t)
-                     ((and completion (zerop (length localname))
-                           (memq operation
-                                 '(expand-file-name file-name-as-directory)))
-                      filename)
-                     ;; Propagate the error.
-                     (t (signal (car err) (cdr err))))))
-
-               ;; Nothing to do for us.  However, since we are in
-               ;; `tramp-mode', we must suppress the volume letter on
-               ;; MS Windows.
-               (setq result (tramp-run-real-handler operation args))
-               (if (stringp result)
-                   (tramp-drop-volume-letter result)
-                 result)))))
+                         (tramp-run-real-handler operation args))
+                        ((eq result 'suppress)
+                         (let (tramp-message-show-message)
+                           (tramp-message
+                            v 1 "Suppress received in operation %s"
+                            (cons operation args))
+                           (tramp-cleanup-connection v t)
+                           (tramp-run-real-handler operation args)))
+                        (t result)))
+
+                     ;; Trace that somebody has interrupted the operation.
+                     ((debug quit)
+                      (let (tramp-message-show-message)
+                        (tramp-message
+                         v 1 "Interrupt received in operation %s"
+                         (cons operation args)))
+                      ;; Propagate the quit signal.
+                      (tramp-compat-signal (car err) (cdr err)))
+
+                     ;; When we are in completion mode, some failed
+                     ;; operations shall return at least a default
+                     ;; value in order to give the user a chance to
+                     ;; correct the file name in the minibuffer.  In
+                     ;; order to get a full backtrace, one could
+                     ;; apply (setq tramp-debug-on-error t)
+                     (error
+                      (cond
+                       ((and completion (zerop (length localname))
+                             (memq operation
+                                   '(file-exists-p file-directory-p)))
+                        t)
+                       ((and completion (zerop (length localname))
+                             (memq operation
+                                   '(expand-file-name file-name-as-directory)))
+                        filename)
+                       ;; Propagate the error.
+                       (t (tramp-compat-signal (car err) (cdr err))))))
+
+                 ;; Nothing to do for us.  However, since we are in
+                 ;; `tramp-mode', we must suppress the volume letter
+                 ;; on MS Windows.
+                 (setq result (tramp-run-real-handler operation args))
+                 (if (stringp result)
+                     (tramp-drop-volume-letter result)
+                   result))))))
 
       ;; When `tramp-mode' is not enabled, or the file name is quoted,
       ;; we don't do anything.
diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index 55c0132..1d572c1 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -795,6 +795,10 @@ current, and kill the buffer that visits the link."
 (defun vc-default-find-file-hook (_backend)
   nil)
 
+;;;###autoload
+(defvar vc-mutex (make-mutex "vc")
+  "Mutex for vc operations.")
+
 (defun vc-refresh-state ()
   "Refresh the VC state of the current buffer's file.
 
@@ -807,57 +811,76 @@ In the latter case, VC mode is deactivated for this 
buffer."
   (when vc-mode
     (setq vc-mode nil))
   (when buffer-file-name
-    (vc-file-clearprops buffer-file-name)
-    ;; FIXME: Why use a hook?  Why pass it buffer-file-name?
-    (add-hook 'vc-mode-line-hook 'vc-mode-line nil t)
-    (let (backend)
-      (cond
-        ((setq backend (with-demoted-errors (vc-backend buffer-file-name)))
-         ;; Let the backend setup any buffer-local things he needs.
-         (vc-call-backend backend 'find-file-hook)
-       ;; Compute the state and put it in the mode line.
-       (vc-mode-line buffer-file-name backend)
-       (unless vc-make-backup-files
-         ;; Use this variable, not make-backup-files,
-         ;; because this is for things that depend on the file name.
-          (set (make-local-variable 'backup-inhibited) t)))
-       ((let* ((truename (and buffer-file-truename
-                             (expand-file-name buffer-file-truename)))
-              (link-type (and truename
-                              (not (equal buffer-file-name truename))
-                              (vc-backend truename))))
-         (cond ((not link-type) nil)   ;Nothing to do.
-               ((eq vc-follow-symlinks nil)
-                (message
-                 "Warning: symbolic link to %s-controlled source file" 
link-type))
-               ((or (not (eq vc-follow-symlinks 'ask))
-                    ;; Assume we cannot ask, default to yes.
-                    noninteractive
-                    ;; Copied from server-start.  Seems like there should
-                    ;; be a better way to ask "can we get user input?"...
-                    (and (daemonp)
-                         (null (cdr (frame-list)))
-                         (eq (selected-frame) terminal-frame))
-                    ;; If we already visited this file by following
-                    ;; the link, don't ask again if we try to visit
-                    ;; it again.  GUD does that, and repeated questions
-                    ;; are painful.
-                    (get-file-buffer
-                     (abbreviate-file-name
-                      (file-chase-links buffer-file-name))))
-
-                (vc-follow-link)
-                (message "Followed link to %s" buffer-file-name)
-                (vc-refresh-state))
-               (t
-                (if (yes-or-no-p (format
-                                  "Symbolic link to %s-controlled source file; 
follow link? " link-type))
-                    (progn (vc-follow-link)
-                           (message "Followed link to %s" buffer-file-name)
-                           (vc-refresh-state))
-                  (message
-                   "Warning: editing through the link bypasses version 
control")
-                  )))))))))
+    ;; Run it asynchronously.
+    (make-thread
+     (lambda ()
+       ;; Don't let the vc operations disturb each other.  Delay this
+       ;; after all find-file* operations have finished.
+       (with-mutex vc-mutex
+         (vc-file-clearprops buffer-file-name)
+         ;; FIXME: Why use a hook?  Why pass it buffer-file-name?
+         (add-hook 'vc-mode-line-hook 'vc-mode-line nil t)
+         (let (backend)
+           (cond
+            ((setq backend (with-demoted-errors (vc-backend buffer-file-name)))
+             ;; Let the backend setup any buffer-local things he needs.
+             (vc-call-backend backend 'find-file-hook)
+            ;; Compute the state and put it in the mode line.
+            (vc-mode-line buffer-file-name backend)
+            (unless vc-make-backup-files
+              ;; Use this variable, not make-backup-files,
+              ;; because this is for things that depend on the file name.
+               (set (make-local-variable 'backup-inhibited) t)))
+            ((let* ((truename (and buffer-file-truename
+                                  (expand-file-name buffer-file-truename)))
+                   (link-type (and truename
+                                   (not (equal buffer-file-name truename))
+                                   (vc-backend truename))))
+              (cond ((not link-type) nil)      ;Nothing to do.
+                    ((eq vc-follow-symlinks nil)
+                     (message
+                      "Warning: symbolic link to %s-controlled source file"
+                       link-type))
+                    ((or (not (eq vc-follow-symlinks 'ask))
+                         ;; Assume we cannot ask, default to yes.
+                         noninteractive
+                         ;; Copied from server-start.  Seems like
+                         ;; there should be a better way to ask "can
+                         ;; we get user input?"...
+                         (and (daemonp)
+                              (null (cdr (frame-list)))
+                              (eq (selected-frame) terminal-frame))
+                         ;; If we already visited this file by
+                         ;; following the link, don't ask again if we
+                         ;; try to visit it again.  GUD does that,
+                         ;; and repeated questions are painful.
+                         (get-file-buffer
+                          (abbreviate-file-name
+                           (file-chase-links buffer-file-name))))
+
+                     (vc-follow-link)
+                     (message "Followed link to %s" buffer-file-name)
+                     (vc-refresh-state))
+                    (t
+                     (if (yes-or-no-p
+                           (format
+                            (concat
+                            "Symbolic link to %s-controlled source file; "
+                             "follow link? ")
+                            link-type))
+                         (progn (vc-follow-link)
+                                (message
+                                  "Followed link to %s" buffer-file-name)
+                                (vc-refresh-state))
+                       (message
+                         (concat
+                         "Warning: editing through the link "
+                          "bypasses version control")))))))))))
+     ;; The thread name.
+     (concat "vc-refresh-state " buffer-file-name))
+
+    ;; Give other threads a chance to run.
+    (thread-yield)))
 
 (add-hook 'find-file-hook #'vc-refresh-state)
 (define-obsolete-function-alias 'vc-find-file-hook 'vc-refresh-state "25.1")
diff --git a/test/lisp/net/tramp-archive-tests.el 
b/test/lisp/net/tramp-archive-tests.el
index 0a8716b..0abb4f9 100644
--- a/test/lisp/net/tramp-archive-tests.el
+++ b/test/lisp/net/tramp-archive-tests.el
@@ -802,7 +802,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
                 (zerop (nth 1 fsi))
                 (zerop (nth 2 fsi))))))
 
-(ert-deftest tramp-archive-test43-auto-load ()
+(ert-deftest tramp-archive-test44-auto-load ()
   "Check that `tramp-archive' autoloads properly."
   (skip-unless tramp-archive-enabled)
   ;; Autoloading tramp-archive works since Emacs 27.1.
@@ -832,7 +832,7 @@ This tests also `file-executable-p', `file-writable-p' and 
`set-file-modes'."
          (mapconcat 'shell-quote-argument load-path " -L ")
          (shell-quote-argument (format code file)))))))))
 
-(ert-deftest tramp-archive-test43-delay-load ()
+(ert-deftest tramp-archive-test44-delay-load ()
   "Check that `tramp-archive' is loaded lazily, only when needed."
   (skip-unless tramp-archive-enabled)
   ;; Autoloading tramp-archive works since Emacs 27.1.
diff --git a/test/lisp/net/tramp-tests.el b/test/lisp/net/tramp-tests.el
index 5c5eff8..f3e97b5 100644
--- a/test/lisp/net/tramp-tests.el
+++ b/test/lisp/net/tramp-tests.el
@@ -5053,8 +5053,107 @@ process sentinels.  They shall not disturb each other."
         (ignore-errors (cancel-timer timer))
         (ignore-errors (delete-directory tmp-name 'recursive)))))))
 
+(ert-deftest tramp-test43-threads ()
+  "Check that Tramp cooperates with threads."
+  (skip-unless (tramp--test-enabled))
+  (skip-unless (featurep 'threads))
+  (skip-unless (= (length (all-threads)) 1))
+  (skip-unless (not (thread-last-error)))
+
+  ;; We cannot bind the variables dynamically; they are used in the threads.
+  (defvar tmp-name1 (tramp--test-make-temp-name))
+  (defvar tmp-name2 (tramp--test-make-temp-name))
+  (defvar tmp-mutex (make-mutex "mutex"))
+  (defvar tmp-condvar1 (make-condition-variable tmp-mutex "condvar1"))
+  (defvar tmp-condvar2 (make-condition-variable tmp-mutex "condvar2"))
+
+  ;; Rename simple file.
+  (unwind-protect
+      (let (tmp-thread1 tmp-thread2)
+       (write-region "foo" nil tmp-name1)
+       (should (file-exists-p tmp-name1))
+       (should-not (file-exists-p tmp-name2))
+
+       (should (mutexp tmp-mutex))
+       (should (condition-variable-p tmp-condvar1))
+       (should (condition-variable-p tmp-condvar2))
+
+       ;; This thread renames `tmp-name1' to `tmp-name2' twice.
+       (setq
+        tmp-thread1
+        (make-thread
+         (lambda ()
+           ;; Rename first time.
+           (rename-file tmp-name1 tmp-name2)
+           ;; Notify thread2.
+           (with-mutex (condition-mutex tmp-condvar2)
+             (condition-notify tmp-condvar2 t))
+           ;; Rename second time, once we've got notification from thread2.
+           (with-mutex (condition-mutex tmp-condvar1)
+             (condition-wait tmp-condvar1))
+           (rename-file tmp-name1 tmp-name2))
+         "thread1"))
+
+       (should (threadp tmp-thread1))
+       (should (thread-alive-p tmp-thread1))
+
+       ;; This thread renames `tmp-name2' to `tmp-name1' twice.
+       (setq
+        tmp-thread2
+        (make-thread
+         (lambda ()
+           ;; Rename first time, once we've got notification from thread1.
+           (with-mutex (condition-mutex tmp-condvar2)
+             (condition-wait tmp-condvar2))
+           (rename-file tmp-name2 tmp-name1)
+           ;; Notify thread1.
+           (with-mutex (condition-mutex tmp-condvar1)
+             (condition-notify tmp-condvar1 t))
+           ;; Rename second time, once we've got notification from
+           ;; the main thread.
+           (with-mutex (condition-mutex tmp-condvar2)
+             (condition-wait tmp-condvar2))
+           (rename-file tmp-name2 tmp-name1))
+         "thread2"))
+
+       (should (threadp tmp-thread2))
+       (should (thread-alive-p tmp-thread2))
+       (should (= (length (all-threads)) 3))
+
+       ;; Wait for thread1.
+       (thread-join tmp-thread1)
+       ;; Checks.
+       (should-not (thread-alive-p tmp-thread1))
+       (should (= (length (all-threads)) 2))
+       (should-not (thread-last-error))
+       (should (file-exists-p tmp-name2))
+       (should-not (file-exists-p tmp-name1))
+
+       ;; Notify thread2.
+       (with-mutex (condition-mutex tmp-condvar2)
+         (condition-notify tmp-condvar2 t))
+
+       ;; Wait for thread2.
+       (thread-join tmp-thread2)
+       ;; Checks.
+       (should-not (thread-alive-p tmp-thread2))
+       (should (= (length (all-threads)) 1))
+       (should-not (thread-last-error))
+       (should (file-exists-p tmp-name1))
+       (should-not (file-exists-p tmp-name2)))
+
+    ;; Cleanup.
+    (ignore-errors (delete-file tmp-name1))
+    (ignore-errors (delete-file tmp-name2))
+    ;; We could have spurious threads still running; wait for them to die.
+    (while (cdr (all-threads))
+      (thread-signal (cadr (all-threads)) 'error nil)
+      (thread-yield))
+    ;; Cleanup errors.
+    (thread-last-error 'cleanup)))
+
 ;; This test is inspired by Bug#29163.
-(ert-deftest tramp-test43-auto-load ()
+(ert-deftest tramp-test44-auto-load ()
   "Check that Tramp autoloads properly."
   (let ((default-directory (expand-file-name temporary-file-directory))
        (code
@@ -5072,7 +5171,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat 'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test43-delay-load ()
+(ert-deftest tramp-test44-delay-load ()
   "Check that Tramp is loaded lazily, only when needed."
   ;; The autoloaded Tramp objects are different since Emacs 26.1.  We
   ;; cannot test older Emacsen, therefore.
@@ -5105,7 +5204,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat 'shell-quote-argument load-path " -L ")
          (shell-quote-argument (format code tm)))))))))
 
-(ert-deftest tramp-test43-recursive-load ()
+(ert-deftest tramp-test44-recursive-load ()
   "Check that Tramp does not fail due to recursive load."
   (skip-unless (tramp--test-enabled))
 
@@ -5129,7 +5228,7 @@ process sentinels.  They shall not disturb each other."
          (mapconcat 'shell-quote-argument load-path " -L ")
          (shell-quote-argument code))))))))
 
-(ert-deftest tramp-test43-remote-load-path ()
+(ert-deftest tramp-test44-remote-load-path ()
   "Check that Tramp autoloads its packages with remote `load-path'."
   ;; The autoloaded Tramp objects are different since Emacs 26.1.  We
   ;; cannot test older Emacsen, therefore.
@@ -5157,7 +5256,7 @@ process sentinels.  They shall not disturb each other."
        (mapconcat 'shell-quote-argument load-path " -L ")
        (shell-quote-argument code)))))))
 
-(ert-deftest tramp-test44-unload ()
+(ert-deftest tramp-test45-unload ()
   "Check that Tramp and its subpackages unload completely.
 Since it unloads Tramp, it shall be the last test to run."
   :tags '(:expensive-test)



reply via email to

[Prev in Thread] Current Thread [Next in Thread]