aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Stephani2020-03-26 17:22:25 +0100
committerPhilipp Stephani2020-03-26 21:47:25 +0100
commitd28b00476890f791a89b65007e5f20682b3eaa0d (patch)
tree3bb04c984ed5b74e661291b71579fe8d04070f69
parent934b3c9ecc2b91723b9e5826080424ec1a90f264 (diff)
downloademacs-d28b00476890f791a89b65007e5f20682b3eaa0d.tar.gz
emacs-d28b00476890f791a89b65007e5f20682b3eaa0d.zip
Add a module function to open a file descriptor connected to a pipe.
A common complaint about the module API is that modules can't communicate asynchronously with Emacs. While it isn't possible to call arbitrary Emacs functions asynchronously, writing to a pipe should always be fine and is a pretty low-hanging fruit. This patch implements a function that adapts an existing pipe process. That way, users can use familiar tools like process filters or 'accept-process-output'. * src/module-env-28.h: Add 'open_channel' module function. * src/emacs-module.c (module_open_channel): Provide definition for 'open_channel'. (initialize_environment): Use it. * src/process.c (open_channel_for_module): New helper function. (syms_of_process): Define necessary symbol. * test/src/emacs-module-tests.el (module/async-pipe): New unit test. * test/data/emacs-module/mod-test.c (signal_system_error): New helper function. (signal_errno): Use it. (write_to_pipe): New function running in the background. (Fmod_test_async_pipe): New test module function. (emacs_module_init): Export it. * doc/lispref/internals.texi (Module Misc): Document new module function. * doc/lispref/processes.texi (Asynchronous Processes): New anchor for pipe processes. * etc/NEWS: Document 'open_channel' function.
-rw-r--r--doc/lispref/internals.texi14
-rw-r--r--doc/lispref/processes.texi1
-rw-r--r--etc/NEWS4
-rw-r--r--src/emacs-module.c9
-rw-r--r--src/module-env-28.h3
-rw-r--r--src/process.c12
-rw-r--r--src/process.h2
-rw-r--r--test/data/emacs-module/mod-test.c57
-rw-r--r--test/src/emacs-module-tests.el14
9 files changed, 114 insertions, 2 deletions
diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi
index 442f6d156b6..0c24dac7775 100644
--- a/doc/lispref/internals.texi
+++ b/doc/lispref/internals.texi
@@ -2022,6 +2022,20 @@ variable values and buffer content may have been modified in arbitrary
2022ways. 2022ways.
2023@end deftypefn 2023@end deftypefn
2024 2024
2025@anchor{open_channel}
2026@deftypefun int open_channel (emacs_env *@var{env}, emacs_value @var{pipe_process})
2027This function, which is available since Emacs 27, opens a channel to
2028an existing pipe process. @var{pipe_process} must refer to an
2029existing pipe process created by @code{make-pipe-process}. @ref{Pipe
2030Processes}. If successful, the return value will be a new file
2031descriptor that you can use to write to the pipe. Unlike all other
2032module functions, you can use the returned file descriptor from
2033arbitrary threads, even if no module environment is active. You can
2034use the @code{write} function to write to the file descriptor. Once
2035done, close the file descriptor using @code{close}. @ref{Low-Level
2036I/O,,,libc}.
2037@end deftypefun
2038
2025@node Module Nonlocal 2039@node Module Nonlocal
2026@subsection Nonlocal Exits in Modules 2040@subsection Nonlocal Exits in Modules
2027@cindex nonlocal exits, in modules 2041@cindex nonlocal exits, in modules
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index f515213615e..14cd079c563 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -743,6 +743,7 @@ Some file name handlers may not support @code{make-process}. In such
743cases, this function does nothing and returns @code{nil}. 743cases, this function does nothing and returns @code{nil}.
744@end defun 744@end defun
745 745
746@anchor{Pipe Processes}
746@defun make-pipe-process &rest args 747@defun make-pipe-process &rest args
747This function creates a bidirectional pipe which can be attached to a 748This function creates a bidirectional pipe which can be attached to a
748child process. This is useful with the @code{:stderr} keyword of 749child process. This is useful with the @code{:stderr} keyword of
diff --git a/etc/NEWS b/etc/NEWS
index 910d9fa2d23..a2cb4b094e8 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -258,6 +258,10 @@ called when the function object is garbage-collected. Use
258'set_function_finalizer' to set the finalizer and 258'set_function_finalizer' to set the finalizer and
259'get_function_finalizer' to retrieve it. 259'get_function_finalizer' to retrieve it.
260 260
261** Modules can now open a channel to an existing pipe process using
262the new module function 'open_channel'. Modules can use this
263functionality to asynchronously send data back to Emacs.
264
261** 'file-modes', 'set-file-modes', and 'set-file-times' now have an 265** 'file-modes', 'set-file-modes', and 'set-file-times' now have an
262optional argument specifying whether to follow symbolic links. 266optional argument specifying whether to follow symbolic links.
263 267
diff --git a/src/emacs-module.c b/src/emacs-module.c
index 60f16418efa..cdcbe061b53 100644
--- a/src/emacs-module.c
+++ b/src/emacs-module.c
@@ -88,6 +88,7 @@ To add a new module function, proceed as follows:
88#include "dynlib.h" 88#include "dynlib.h"
89#include "coding.h" 89#include "coding.h"
90#include "keyboard.h" 90#include "keyboard.h"
91#include "process.h"
91#include "syssignal.h" 92#include "syssignal.h"
92#include "sysstdio.h" 93#include "sysstdio.h"
93#include "thread.h" 94#include "thread.h"
@@ -977,6 +978,13 @@ module_make_big_integer (emacs_env *env, int sign,
977 return lisp_to_value (env, make_integer_mpz ()); 978 return lisp_to_value (env, make_integer_mpz ());
978} 979}
979 980
981static int
982module_open_channel (emacs_env *env, emacs_value pipe_process)
983{
984 MODULE_FUNCTION_BEGIN (-1);
985 return open_channel_for_module (value_to_lisp (pipe_process));
986}
987
980 988
981/* Subroutines. */ 989/* Subroutines. */
982 990
@@ -1391,6 +1399,7 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv)
1391 env->make_big_integer = module_make_big_integer; 1399 env->make_big_integer = module_make_big_integer;
1392 env->get_function_finalizer = module_get_function_finalizer; 1400 env->get_function_finalizer = module_get_function_finalizer;
1393 env->set_function_finalizer = module_set_function_finalizer; 1401 env->set_function_finalizer = module_set_function_finalizer;
1402 env->open_channel = module_open_channel;
1394 Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); 1403 Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments);
1395 return env; 1404 return env;
1396} 1405}
diff --git a/src/module-env-28.h b/src/module-env-28.h
index a2479a8f744..5d884c148c4 100644
--- a/src/module-env-28.h
+++ b/src/module-env-28.h
@@ -9,3 +9,6 @@
9 void (*set_function_finalizer) (emacs_env *env, emacs_value arg, 9 void (*set_function_finalizer) (emacs_env *env, emacs_value arg,
10 void (*fin) (void *) EMACS_NOEXCEPT) 10 void (*fin) (void *) EMACS_NOEXCEPT)
11 EMACS_ATTRIBUTE_NONNULL (1); 11 EMACS_ATTRIBUTE_NONNULL (1);
12
13 int (*open_channel) (emacs_env *env, emacs_value pipe_process)
14 EMACS_ATTRIBUTE_NONNULL (1);
diff --git a/src/process.c b/src/process.c
index e4e5e57aeee..07881d6c5d3 100644
--- a/src/process.c
+++ b/src/process.c
@@ -8200,6 +8200,17 @@ restore_nofile_limit (void)
8200#endif 8200#endif
8201} 8201}
8202 8202
8203int
8204open_channel_for_module (Lisp_Object process)
8205{
8206 CHECK_PROCESS (process);
8207 CHECK_TYPE (PIPECONN_P (process), Qpipe_process_p, process);
8208 int fd = dup (XPROCESS (process)->open_fd[SUBPROCESS_STDOUT]);
8209 if (fd == -1)
8210 report_file_error ("Cannot duplicate file descriptor", Qnil);
8211 return fd;
8212}
8213
8203 8214
8204/* This is not called "init_process" because that is the name of a 8215/* This is not called "init_process" because that is the name of a
8205 Mach system call, so it would cause problems on Darwin systems. */ 8216 Mach system call, so it would cause problems on Darwin systems. */
@@ -8446,6 +8457,7 @@ amounts of data in one go. */);
8446 DEFSYM (Qinterrupt_process_functions, "interrupt-process-functions"); 8457 DEFSYM (Qinterrupt_process_functions, "interrupt-process-functions");
8447 8458
8448 DEFSYM (Qnull, "null"); 8459 DEFSYM (Qnull, "null");
8460 DEFSYM (Qpipe_process_p, "pipe-process-p");
8449 8461
8450 defsubr (&Sprocessp); 8462 defsubr (&Sprocessp);
8451 defsubr (&Sget_process); 8463 defsubr (&Sget_process);
diff --git a/src/process.h b/src/process.h
index 7884efc5494..a783a31cb86 100644
--- a/src/process.h
+++ b/src/process.h
@@ -300,6 +300,8 @@ extern Lisp_Object remove_slash_colon (Lisp_Object);
300extern void update_processes_for_thread_death (Lisp_Object); 300extern void update_processes_for_thread_death (Lisp_Object);
301extern void dissociate_controlling_tty (void); 301extern void dissociate_controlling_tty (void);
302 302
303extern int open_channel_for_module (Lisp_Object);
304
303INLINE_HEADER_END 305INLINE_HEADER_END
304 306
305#endif /* EMACS_PROCESS_H */ 307#endif /* EMACS_PROCESS_H */
diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c
index ec6948921f2..61733f1ef49 100644
--- a/test/data/emacs-module/mod-test.c
+++ b/test/data/emacs-module/mod-test.c
@@ -30,6 +30,9 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
30#include <string.h> 30#include <string.h>
31#include <time.h> 31#include <time.h>
32 32
33#include <pthread.h>
34#include <unistd.h>
35
33#ifdef HAVE_GMP 36#ifdef HAVE_GMP
34#include <gmp.h> 37#include <gmp.h>
35#else 38#else
@@ -320,9 +323,9 @@ Fmod_test_invalid_finalizer (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
320} 323}
321 324
322static void 325static void
323signal_errno (emacs_env *env, const char *function) 326signal_system_error (emacs_env *env, int error, const char *function)
324{ 327{
325 const char *message = strerror (errno); 328 const char *message = strerror (error);
326 emacs_value message_value = env->make_string (env, message, strlen (message)); 329 emacs_value message_value = env->make_string (env, message, strlen (message));
327 emacs_value symbol = env->intern (env, "file-error"); 330 emacs_value symbol = env->intern (env, "file-error");
328 emacs_value elements[2] 331 emacs_value elements[2]
@@ -331,6 +334,12 @@ signal_errno (emacs_env *env, const char *function)
331 env->non_local_exit_signal (env, symbol, data); 334 env->non_local_exit_signal (env, symbol, data);
332} 335}
333 336
337static void
338signal_errno (emacs_env *env, const char *function)
339{
340 signal_system_error (env, errno, function);
341}
342
334/* A long-running operation that occasionally calls `should_quit' or 343/* A long-running operation that occasionally calls `should_quit' or
335 `process_input'. */ 344 `process_input'. */
336 345
@@ -533,6 +542,49 @@ Fmod_test_function_finalizer_calls (emacs_env *env, ptrdiff_t nargs,
533 return env->funcall (env, Flist, 2, list_args); 542 return env->funcall (env, Flist, 2, list_args);
534} 543}
535 544
545static void *
546write_to_pipe (void *arg)
547{
548 /* We sleep a bit to test that writing to a pipe is indeed possible
549 if no environment is active. */
550 const struct timespec sleep = {0, 500000000};
551 if (nanosleep (&sleep, NULL) != 0)
552 perror ("nanosleep");
553 FILE *stream = arg;
554 if (fputs ("data from thread", stream) < 0)
555 perror ("fputs");
556 if (fclose (stream) != 0)
557 perror ("close");
558 return NULL;
559}
560
561static emacs_value
562Fmod_test_async_pipe (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
563 void *data)
564{
565 assert (nargs == 1);
566 int fd = env->open_channel (env, args[0]);
567 if (env->non_local_exit_check (env) != emacs_funcall_exit_return)
568 return NULL;
569 FILE *stream = fdopen (fd, "w");
570 if (stream == NULL)
571 {
572 signal_errno (env, "fdopen");
573 return NULL;
574 }
575 pthread_t thread;
576 int error
577 = pthread_create (&thread, NULL, write_to_pipe, stream);
578 if (error != 0)
579 {
580 signal_system_error (env, error, "pthread_create");
581 if (fclose (stream) != 0)
582 perror ("fclose");
583 return NULL;
584 }
585 return env->intern (env, "nil");
586}
587
536/* Lisp utilities for easier readability (simple wrappers). */ 588/* Lisp utilities for easier readability (simple wrappers). */
537 589
538/* Provide FEATURE to Emacs. */ 590/* Provide FEATURE to Emacs. */
@@ -614,6 +666,7 @@ emacs_module_init (struct emacs_runtime *ert)
614 Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL); 666 Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL);
615 DEFUN ("mod-test-function-finalizer-calls", 667 DEFUN ("mod-test-function-finalizer-calls",
616 Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL); 668 Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL);
669 DEFUN ("mod-test-async-pipe", Fmod_test_async_pipe, 1, 1, NULL, NULL);
617 670
618#undef DEFUN 671#undef DEFUN
619 672
diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el
index 48d2e86a605..1f91795e1e6 100644
--- a/test/src/emacs-module-tests.el
+++ b/test/src/emacs-module-tests.el
@@ -424,4 +424,18 @@ See Bug#36226."
424 ;; but at least one. 424 ;; but at least one.
425 (should (> valid-after valid-before))))) 425 (should (> valid-after valid-before)))))
426 426
427(ert-deftest module/async-pipe ()
428 "Check that writing data from another thread works."
429 (with-temp-buffer
430 (let ((process (make-pipe-process :name "module/async-pipe"
431 :buffer (current-buffer)
432 :coding 'utf-8-unix
433 :noquery t)))
434 (unwind-protect
435 (progn
436 (mod-test-async-pipe process)
437 (should (accept-process-output process 1))
438 (should (equal (buffer-string) "data from thread")))
439 (delete-process process)))))
440
427;;; emacs-module-tests.el ends here 441;;; emacs-module-tests.el ends here