From bc2b411875c27249060f0c2fd5cb001dc6b15e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 12 Feb 2023 02:20:44 -0300 Subject: [PATCH] Process Async IO. Windows fixes. --- src/eepp/network/uri.cpp | 9 +- src/eepp/system/process.cpp | 17 ++-- src/eepp/ui/uicodeeditor.cpp | 8 +- src/thirdparty/subprocess/subprocess.h | 93 +++++++++++++++++++ .../ecode/plugins/lsp/lspclientserver.cpp | 3 +- 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/src/eepp/network/uri.cpp b/src/eepp/network/uri.cpp index ec6d0ced1..d4b84b017 100644 --- a/src/eepp/network/uri.cpp +++ b/src/eepp/network/uri.cpp @@ -552,6 +552,11 @@ unsigned short URI::getWellKnownPort() const { #if EE_PLATFORM == EE_PLATFORM_WIN void URI::parse( const std::string& _uri ) { std::string uri( _uri ); + // Is a local path without hostname but it's correctly formatted? + if ( String::startsWith( uri, "file://" ) && uri.size() >= 9 && uri[7] != '/' && + uri[8] == ':' ) { + uri.insert( uri.begin() + 7, '/' ); + } String::replaceAll( uri, "\\", "/" ); #else void URI::parse( const std::string& uri ) { @@ -645,10 +650,6 @@ void URI::parseHostAndPort( std::string::const_iterator& it, mPort = getWellKnownPort(); } mHost = host; -#if EE_PLATFORM == EE_PLATFORM_WIN - if ( mScheme == "file" && !mHost.empty() && mHost[mHost.size() - 1] != ':' ) - mHost = "/" + mHost + ":"; -#endif String::toLowerInPlace( mHost ); } diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index becde59eb..088faebb8 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -5,6 +5,7 @@ #include #include #include + #if EE_PLATFORM == EE_PLATFORM_MACOSX #define SUBPROCESS_USE_POSIX_SPAWN #endif @@ -75,12 +76,13 @@ bool Process::create( const std::string& command, const Uint32& options, !workingDirectory.empty() ? workingDirectory.c_str() : nullptr, PROCESS_PTR ); - return ret; } - return 0 == subprocess_create_ex( - strings.data(), options, nullptr, - !workingDirectory.empty() ? workingDirectory.c_str() : nullptr, PROCESS_PTR ); + auto ret = + 0 == subprocess_create_ex( strings.data(), options, nullptr, + !workingDirectory.empty() ? workingDirectory.c_str() : nullptr, + PROCESS_PTR ); + return ret; } size_t Process::readAllStdOut( std::string& buffer ) { @@ -136,12 +138,7 @@ size_t Process::write( const char* buffer, const size_t& size ) { if ( mShuttingDown ) return 0; Lock l( mStdInMutex ); - FILE* stdInFile = subprocess_stdin( PROCESS_PTR ); - if ( !stdInFile ) - return 0; - int ret = fwrite( buffer, 1, size, stdInFile ); - fflush( stdInFile ); - return ret; + return subprocess_write_stdin( PROCESS_PTR, (char* const)buffer, size ); } size_t Process::write( const std::string& buffer ) { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index a50e4cfc5..459610367 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -2826,8 +2826,8 @@ void UICodeEditor::drawWhitespaces( const std::pair& lineRange, Color color( Color( mWhitespaceColor ).blendAlpha( mAlpha ) ); unsigned int fontSize = getCharacterSize(); // We use the GlyphDrawable since can batch the draw calls instead of Text. - GlyphDrawable* adv = mFont->getGlyphDrawable( L'»', fontSize ); - GlyphDrawable* cpoint = mFont->getGlyphDrawable( L'·', fontSize ); + GlyphDrawable* adv = mFont->getGlyphDrawable( 187 /*'»'*/, fontSize ); + GlyphDrawable* cpoint = mFont->getGlyphDrawable( 183 /*'·'*/, fontSize ); Float tabCenter = ( tabWidth - adv->getPixelsSize().getWidth() ) * 0.5f; adv->setDrawMode( GlyphDrawable::DrawMode::Text ); cpoint->setDrawMode( GlyphDrawable::DrawMode::Text ); @@ -2903,9 +2903,9 @@ void UICodeEditor::drawLineEndings( const std::pair& lineRange, Color color( Color( mWhitespaceColor ).blendAlpha( mAlpha ) ); unsigned int fontSize = getCharacterSize(); - GlyphDrawable* nl = mFont->getGlyphDrawable( L'↴', fontSize ); + GlyphDrawable* nl = mFont->getGlyphDrawable( 8628 /*'↴'*/, fontSize ); if ( nl->getPixelsSize() == Sizef::Zero ) - nl = mFont->getGlyphDrawable( L'¬', fontSize ); + nl = mFont->getGlyphDrawable( 172 /* '¬'*/, fontSize ); nl->setDrawMode( GlyphDrawable::DrawMode::Text ); nl->setColor( color ); for ( int index = lineRange.first; index <= lineRange.second; index++ ) { diff --git a/src/thirdparty/subprocess/subprocess.h b/src/thirdparty/subprocess/subprocess.h index ed93fb0fb..3a3926ca0 100644 --- a/src/thirdparty/subprocess/subprocess.h +++ b/src/thirdparty/subprocess/subprocess.h @@ -201,6 +201,10 @@ subprocess_weak unsigned subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +subprocess_weak unsigned +subprocess_write_stdin(struct subprocess_s *const process, char *const buffer, + unsigned size); + /// @brief Returns if the subprocess is currently still alive and executing. /// @param process The process to check. /// @return If the process is still alive non-zero is returned. @@ -225,6 +229,7 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #include #include #include +#include #endif #if defined(_WIN32) @@ -320,6 +325,8 @@ __declspec(dllimport) void *__stdcall CreateNamedPipeA( unsigned long, unsigned long, LPSECURITY_ATTRIBUTES); __declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long, unsigned long *, LPOVERLAPPED); +__declspec(dllimport) int __stdcall WriteFile(void *, const void *, unsigned long, + unsigned long *, LPOVERLAPPED); __declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void); __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); __declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long, @@ -342,6 +349,7 @@ __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, unsigned long *, int); +__declspec(dllimport) int __stdcall FlushFileBuffers(void *); #if defined(_DLL) #define SUBPROCESS_DLLIMPORT __declspec(dllimport) @@ -385,6 +393,7 @@ struct subprocess_s { void *hProcess; void *hStdInput; void *hEventOutput; + void *hEventInput; void *hEventError; #else pid_t child; @@ -392,15 +401,18 @@ struct subprocess_s { #endif subprocess_size_t alive; + int options; }; #ifdef __clang__ #pragma clang diagnostic pop #endif #if defined(_WIN32) + subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr); int subprocess_create_named_pipe_helper(void **rd, void **wr) { const unsigned long pipeAccessInbound = 0x00000001; + const unsigned long pipeAccessOutbound = 0x00000002; const unsigned long fileFlagOverlapped = 0x40000000; const unsigned long pipeTypeByte = 0x00000000; const unsigned long pipeWait = 0x00000000; @@ -412,8 +424,10 @@ int subprocess_create_named_pipe_helper(void **rd, void **wr) { struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), SUBPROCESS_NULL, 1}; char name[256] = {0}; + char name2[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; + const long unique2 = index++; #if _MSC_VER < 1900 #pragma warning(push, 1) @@ -455,6 +469,22 @@ int subprocess_create(const char *const commandLine[], int options, SUBPROCESS_NULL, out_process); } +#if !defined(_WIN32) +subprocess_weak +void subprocess_set_async(struct subprocess_s *const out_process) { + if (out_process->options & subprocess_option_enable_async) { + int stdin_fd = fileno( out_process->stdin_file ); + fcntl( stdin_fd, F_SETFL, fcntl( stdin_fd, F_GETFL ) | O_NONBLOCK ); + int stdout_fd = fileno( out_process->stdout_file ); + fcntl( stdout_fd, F_SETFL, fcntl( stdout_fd, F_GETFL ) | O_NONBLOCK ); + if (!(out_process->options & subprocess_option_combined_stdout_stderr)) { + int stderr_fd = fileno( out_process->stderr_file ); + fcntl( stderr_fd, F_SETFL, fcntl( stderr_fd, F_GETFL ) | O_NONBLOCK ); + } + } +} +#endif + int subprocess_create_ex(const char *const commandLine[], int options, const char *const environment[], const char* working_directory, @@ -622,6 +652,9 @@ int subprocess_create_ex(const char *const commandLine[], int options, } if (options & subprocess_option_enable_async) { + out_process->hEventInput = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventOutput = CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, SUBPROCESS_NULL); @@ -629,6 +662,7 @@ int subprocess_create_ex(const char *const commandLine[], int options, CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, SUBPROCESS_NULL); } else { + out_process->hEventInput = SUBPROCESS_NULL; out_process->hEventOutput = SUBPROCESS_NULL; out_process->hEventError = SUBPROCESS_NULL; } @@ -736,6 +770,8 @@ int subprocess_create_ex(const char *const commandLine[], int options, out_process->alive = 1; + out_process->options = options; + return 0; #else int stdinfd[2]; @@ -894,8 +930,12 @@ int subprocess_create_ex(const char *const commandLine[], int options, out_process->alive = 1; + out_process->options = options; + posix_spawn_file_actions_destroy(&actions); + subprocess_set_async(out_process); + return 0; #else child = fork(); @@ -973,6 +1013,10 @@ int subprocess_create_ex(const char *const commandLine[], int options, out_process->alive = 1; + out_process->options = options; + + subprocess_set_async(out_process); + return 0; } #endif @@ -1082,6 +1126,10 @@ int subprocess_destroy(struct subprocess_s *const process) { CloseHandle(process->hStdInput); } + if (process->hEventInput) { + CloseHandle(process->hEventInput); + } + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } @@ -1253,6 +1301,51 @@ int subprocess_alive(struct subprocess_s *const process) { return is_alive; } +subprocess_weak unsigned +subprocess_write_stdin(struct subprocess_s *const process, char *const buffer, + unsigned size) { +#if defined(_WIN32) + void* handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdin_file))); + unsigned long bytes_write = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventInput; + + if (!WriteFile(handle, buffer, size, &bytes_write, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async write! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_write, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } else { + FlushFileBuffers(handle); + } + return SUBPROCESS_CAST(unsigned, bytes_write); +#else + const int fd = fileno(process->stdin_file); + const ssize_t ret = write(fd, buffer, size); + if (ret > 0) { + fsync(fd); + } else if (ret < 0) { + return 0; + } + return ret; +#endif +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 3a89b10dc..1fc119973 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -947,7 +947,8 @@ bool LSPClientServer::start() { mLSP.commandParameters = " " + mLSP.commandParameters; cmd += mLSP.commandParameters; } - bool ret = mProcess.create( cmd, Process::getDefaultOptions(), {}, mRootPath ); + bool ret = + mProcess.create( cmd, Process::getDefaultOptions() | Process::EnableAsync, {}, mRootPath ); if ( ret && mProcess.isAlive() ) { mProcess.startAsyncRead( [this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); },