From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.ciao.gmane.io!not-for-mail From: Philipp Stephani Newsgroups: gmane.emacs.devel Subject: [PATCH] Add a module function to open a file descriptor connected to a pipe. Date: Thu, 26 Mar 2020 17:30:21 +0100 Message-ID: <20200326163021.111847-1-phst@google.com> Mime-Version: 1.0 Content-Transfer-Encoding: 8bit Injection-Info: ciao.gmane.io; posting-host="ciao.gmane.io:159.69.161.202"; logging-data="127416"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Philipp Stephani To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Thu Mar 26 17:31:18 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jHVPZ-000X3f-RE for ged-emacs-devel@m.gmane-mx.org; Thu, 26 Mar 2020 17:31:17 +0100 Original-Received: from localhost ([::1]:56386 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jHVPY-0001VI-RY for ged-emacs-devel@m.gmane-mx.org; Thu, 26 Mar 2020 12:31:16 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:33236) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jHVOu-00011A-Ki for emacs-devel@gnu.org; Thu, 26 Mar 2020 12:30:38 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1jHVOs-00013w-LA for emacs-devel@gnu.org; Thu, 26 Mar 2020 12:30:36 -0400 Original-Received: from mail-wr1-x442.google.com ([2a00:1450:4864:20::442]:39863) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1jHVOs-00013O-Cn for emacs-devel@gnu.org; Thu, 26 Mar 2020 12:30:34 -0400 Original-Received: by mail-wr1-x442.google.com with SMTP id p10so8582991wrt.6 for ; Thu, 26 Mar 2020 09:30:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=Dz+icwqozJvqHyxVnRDZoI2j+3XTvEdPAyIJiA1xIzo=; b=gqxGaaR1yWwHBP4ms4rwznILOWp77i2KvZcDrs20rLVOSDQdUwSHlAVS9/VfetbABo wc0eqlWjY9C5eUHnFXoHkNViQXElrMBHo7iJOmDBHBO+O0RfiDbNTaPJTnzU+46Vd7ty KQESzaB/KBKyzjKwQ66bJ5rpvl3LpsVcwjBjm0psMBDkIjGvCE+2KnVGz3xXYoiQpAZB pyDXuho4mf4cW7Ae+lRLhfEV8QTxVIUcjg3I78kMpCETchiNdi5I30Mvo+aaPBA1+iW5 5C+EzX+1q8Sbg/KGWYKHzR1E20wj9sy5EhlTJRQEeYZIoJU0f2Ee9jYAYwNlLBSYk3FB a+6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=Dz+icwqozJvqHyxVnRDZoI2j+3XTvEdPAyIJiA1xIzo=; b=PnvumjdtPE9qBIAXitBME+mzdQ5pe1v9P2d9QVwqxtnXYGuzQPk7RMtA9TiyQlYJTK tePpcqDa006i2gXr7I9/UQatCrcWod7a/TNTePi2SFQEvpjI0Ezr/74gqqiuIYVxQrk9 aM+5ptlclmII7xDD2/1TDkGE1U/f3LTE/lxlJLhBuEp16mceVBLrcVowjwoLV6GjcNnH 4uuf/Zedj3ZR7vCkjZcvV4hNtHmkWavNStV6eaOtpg+MzuU5urThHwXC5Kg0dNHd634g HTKjLZrNBzf9qHq7oJcjp0bVpSYoHD8XOM/Phwvy+6YRYfmhSMhKh+3CpTt1QegtLwhk yk/A== X-Gm-Message-State: ANhLgQ0dk3hW9HUrC9jwG7500GDbWZ3jXmgrSJzl6ftrygY4nMPAvWlp 9qzb3tJUT4zDiAM2WqAu8KVM3g55 X-Google-Smtp-Source: ADFU+vtKrNxljIrRZFfs7eFesmi/k8qhuKlBH2jnMlMQQXeJszsPU06f8oaVWP87BHmxnhQaFtLdPg== X-Received: by 2002:a5d:4f92:: with SMTP id d18mr10068643wru.400.1585240231915; Thu, 26 Mar 2020 09:30:31 -0700 (PDT) Original-Received: from phst1.cm.cablesurf.de ([2a02:2455:2a2:100:c351:ffcc:5d8e:4288]) by smtp.gmail.com with ESMTPSA id z19sm4442765wrg.28.2020.03.26.09.30.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 26 Mar 2020 09:30:31 -0700 (PDT) X-Google-Original-From: Philipp Stephani X-Mailer: git-send-email 2.25.1.696.g5e7596f4ac-goog X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2a00:1450:4864:20::442 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:245810 Archived-At: 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. --- doc/lispref/internals.texi | 14 ++++++++ doc/lispref/processes.texi | 1 + src/emacs-module.c | 9 +++++ src/module-env-28.h | 3 ++ src/process.c | 12 +++++++ src/process.h | 2 ++ test/data/emacs-module/mod-test.c | 57 +++++++++++++++++++++++++++++-- test/src/emacs-module-tests.el | 14 ++++++++ 8 files changed, 110 insertions(+), 2 deletions(-) diff --git a/doc/lispref/internals.texi b/doc/lispref/internals.texi index 442f6d156b..0c24dac777 100644 --- a/doc/lispref/internals.texi +++ b/doc/lispref/internals.texi @@ -2022,6 +2022,20 @@ Module Misc ways. @end deftypefn +@anchor{open_channel} +@deftypefun int open_channel (emacs_env *@var{env}, emacs_value @var{pipe_process}) +This function, which is available since Emacs 27, opens a channel to +an existing pipe process. @var{pipe_process} must refer to an +existing pipe process created by @code{make-pipe-process}. @ref{Pipe +Processes}. If successful, the return value will be a new file +descriptor that you can use to write to the pipe. Unlike all other +module functions, you can use the returned file descriptor from +arbitrary threads, even if no module environment is active. You can +use the @code{write} function to write to the file descriptor. Once +done, close the file descriptor using @code{close}. @ref{Low-Level +I/O,,,libc}. +@end deftypefun + @node Module Nonlocal @subsection Nonlocal Exits in Modules @cindex nonlocal exits, in modules diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi index f515213615..14cd079c56 100644 --- a/doc/lispref/processes.texi +++ b/doc/lispref/processes.texi @@ -743,6 +743,7 @@ Asynchronous Processes cases, this function does nothing and returns @code{nil}. @end defun +@anchor{Pipe Processes} @defun make-pipe-process &rest args This function creates a bidirectional pipe which can be attached to a child process. This is useful with the @code{:stderr} keyword of diff --git a/src/emacs-module.c b/src/emacs-module.c index 60f16418ef..cdcbe061b5 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -88,6 +88,7 @@ Copyright (C) 2015-2020 Free Software Foundation, Inc. #include "dynlib.h" #include "coding.h" #include "keyboard.h" +#include "process.h" #include "syssignal.h" #include "sysstdio.h" #include "thread.h" @@ -977,6 +978,13 @@ module_make_big_integer (emacs_env *env, int sign, return lisp_to_value (env, make_integer_mpz ()); } +static int +module_open_channel (emacs_env *env, emacs_value pipe_process) +{ + MODULE_FUNCTION_BEGIN (-1); + return open_channel_for_module (value_to_lisp (pipe_process)); +} + /* Subroutines. */ @@ -1391,6 +1399,7 @@ initialize_environment (emacs_env *env, struct emacs_env_private *priv) env->make_big_integer = module_make_big_integer; env->get_function_finalizer = module_get_function_finalizer; env->set_function_finalizer = module_set_function_finalizer; + env->open_channel = module_open_channel; Vmodule_environments = Fcons (make_mint_ptr (env), Vmodule_environments); return env; } diff --git a/src/module-env-28.h b/src/module-env-28.h index a2479a8f74..5d884c148c 100644 --- a/src/module-env-28.h +++ b/src/module-env-28.h @@ -9,3 +9,6 @@ void (*set_function_finalizer) (emacs_env *env, emacs_value arg, void (*fin) (void *) EMACS_NOEXCEPT) EMACS_ATTRIBUTE_NONNULL (1); + + int (*open_channel) (emacs_env *env, emacs_value pipe_process) + EMACS_ATTRIBUTE_NONNULL (1); diff --git a/src/process.c b/src/process.c index e4e5e57aee..07881d6c5d 100644 --- a/src/process.c +++ b/src/process.c @@ -8200,6 +8200,17 @@ restore_nofile_limit (void) #endif } +int +open_channel_for_module (Lisp_Object process) +{ + CHECK_PROCESS (process); + CHECK_TYPE (PIPECONN_P (process), Qpipe_process_p, process); + int fd = dup (XPROCESS (process)->open_fd[SUBPROCESS_STDOUT]); + if (fd == -1) + report_file_error ("Cannot duplicate file descriptor", Qnil); + return fd; +} + /* This is not called "init_process" because that is the name of a Mach system call, so it would cause problems on Darwin systems. */ @@ -8446,6 +8457,7 @@ syms_of_process (void) DEFSYM (Qinterrupt_process_functions, "interrupt-process-functions"); DEFSYM (Qnull, "null"); + DEFSYM (Qpipe_process_p, "pipe-process-p"); defsubr (&Sprocessp); defsubr (&Sget_process); diff --git a/src/process.h b/src/process.h index 7884efc549..a783a31cb8 100644 --- a/src/process.h +++ b/src/process.h @@ -300,6 +300,8 @@ pset_gnutls_cred_type (struct Lisp_Process *p, Lisp_Object val) extern void update_processes_for_thread_death (Lisp_Object); extern void dissociate_controlling_tty (void); +extern int open_channel_for_module (Lisp_Object); + INLINE_HEADER_END #endif /* EMACS_PROCESS_H */ diff --git a/test/data/emacs-module/mod-test.c b/test/data/emacs-module/mod-test.c index ec6948921f..61733f1ef4 100644 --- a/test/data/emacs-module/mod-test.c +++ b/test/data/emacs-module/mod-test.c @@ -30,6 +30,9 @@ #include #include +#include +#include + #ifdef HAVE_GMP #include #else @@ -320,9 +323,9 @@ Fmod_test_invalid_finalizer (emacs_env *env, ptrdiff_t nargs, emacs_value *args, } static void -signal_errno (emacs_env *env, const char *function) +signal_system_error (emacs_env *env, int error, const char *function) { - const char *message = strerror (errno); + const char *message = strerror (error); emacs_value message_value = env->make_string (env, message, strlen (message)); emacs_value symbol = env->intern (env, "file-error"); emacs_value elements[2] @@ -331,6 +334,12 @@ signal_errno (emacs_env *env, const char *function) env->non_local_exit_signal (env, symbol, data); } +static void +signal_errno (emacs_env *env, const char *function) +{ + signal_system_error (env, errno, function); +} + /* A long-running operation that occasionally calls `should_quit' or `process_input'. */ @@ -533,6 +542,49 @@ Fmod_test_function_finalizer_calls (emacs_env *env, ptrdiff_t nargs, return env->funcall (env, Flist, 2, list_args); } +static void * +write_to_pipe (void *arg) +{ + /* We sleep a bit to test that writing to a pipe is indeed possible + if no environment is active. */ + const struct timespec sleep = {0, 500000000}; + if (nanosleep (&sleep, NULL) != 0) + perror ("nanosleep"); + FILE *stream = arg; + if (fputs ("data from thread", stream) < 0) + perror ("fputs"); + if (fclose (stream) != 0) + perror ("close"); + return NULL; +} + +static emacs_value +Fmod_test_async_pipe (emacs_env *env, ptrdiff_t nargs, emacs_value *args, + void *data) +{ + assert (nargs == 1); + int fd = env->open_channel (env, args[0]); + if (env->non_local_exit_check (env) != emacs_funcall_exit_return) + return NULL; + FILE *stream = fdopen (fd, "w"); + if (stream == NULL) + { + signal_errno (env, "fdopen"); + return NULL; + } + pthread_t thread; + int error + = pthread_create (&thread, NULL, write_to_pipe, stream); + if (error != 0) + { + signal_system_error (env, error, "pthread_create"); + if (fclose (stream) != 0) + perror ("fclose"); + return NULL; + } + return env->intern (env, "nil"); +} + /* Lisp utilities for easier readability (simple wrappers). */ /* Provide FEATURE to Emacs. */ @@ -614,6 +666,7 @@ #define DEFUN(lsym, csym, amin, amax, doc, data) \ Fmod_test_make_function_with_finalizer, 0, 0, NULL, NULL); DEFUN ("mod-test-function-finalizer-calls", Fmod_test_function_finalizer_calls, 0, 0, NULL, NULL); + DEFUN ("mod-test-async-pipe", Fmod_test_async_pipe, 1, 1, NULL, NULL); #undef DEFUN diff --git a/test/src/emacs-module-tests.el b/test/src/emacs-module-tests.el index 48d2e86a60..1f91795e1e 100644 --- a/test/src/emacs-module-tests.el +++ b/test/src/emacs-module-tests.el @@ -424,4 +424,18 @@ module/function-finalizer ;; but at least one. (should (> valid-after valid-before))))) +(ert-deftest module/async-pipe () + "Check that writing data from another thread works." + (with-temp-buffer + (let ((process (make-pipe-process :name "module/async-pipe" + :buffer (current-buffer) + :coding 'utf-8-unix + :noquery t))) + (unwind-protect + (progn + (mod-test-async-pipe process) + (should (accept-process-output process 1)) + (should (equal (buffer-string) "data from thread"))) + (delete-process process))))) + ;;; emacs-module-tests.el ends here -- 2.25.1.696.g5e7596f4ac-goog