From 7b09d63a6f2aa70a5985845a873466b61b77f8ef Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 1 Dec 2019 01:30:40 +0100 Subject: [PATCH 1/6] src: port memory-tracking allocator from QUIC repo This implements a memory-tracking allocator that can be used to provide memory allocation facilities to several thread-safe C libraries, including nghttp2, nghttp3, ngtcp3 and uvwasi. Refs: https://github.com/nodejs/quic/blob/34ee0bc96f804c73cb22b2945a1a78f780938492/src/node_mem.h Co-authored-by: James M Snell --- node.gyp | 2 + src/node_mem-inl.h | 112 +++++++++++++++++++++++++++++++++++++++++++++ src/node_mem.h | 41 +++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 src/node_mem-inl.h create mode 100644 src/node_mem.h diff --git a/node.gyp b/node.gyp index cffd150845e51a..3cee786a6ee502 100644 --- a/node.gyp +++ b/node.gyp @@ -627,6 +627,8 @@ 'src/node_i18n.h', 'src/node_internals.h', 'src/node_main_instance.h', + 'src/node_mem.h', + 'src/node_mem-inl.h', 'src/node_messaging.h', 'src/node_metadata.h', 'src/node_mutex.h', diff --git a/src/node_mem-inl.h b/src/node_mem-inl.h new file mode 100644 index 00000000000000..ad6fc45b36942d --- /dev/null +++ b/src/node_mem-inl.h @@ -0,0 +1,112 @@ +#ifndef SRC_NODE_MEM_INL_H_ +#define SRC_NODE_MEM_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node_mem.h" +#include "node_internals.h" + +namespace node { +namespace mem { + +template +AllocatorStruct NgLibMemoryManager::MakeAllocator() { + return AllocatorStruct { + static_cast(static_cast(this)), + MallocImpl, + FreeImpl, + CallocImpl, + ReallocImpl + }; +} + +template +void* NgLibMemoryManager::ReallocImpl(void* ptr, + size_t size, + void* user_data) { + Class* manager = static_cast(user_data); + + size_t previous_size = 0; + char* original_ptr = nullptr; + + // We prepend each allocated buffer with a size_t containing the full + // size of the allocation. + if (size > 0) size += sizeof(size_t); + + if (ptr != nullptr) { + // We are free()ing or re-allocating. + original_ptr = static_cast(ptr) - sizeof(size_t); + previous_size = *reinterpret_cast(original_ptr); + // This means we called StopTracking() on this pointer before. + if (previous_size == 0) { + // Fall back to the standard Realloc() function. + char* ret = UncheckedRealloc(original_ptr, size); + if (ret != nullptr) + ret += sizeof(size_t); + return ret; + } + } + + manager->CheckAllocatedSize(previous_size); + + char* mem = UncheckedRealloc(original_ptr, size); + + if (mem != nullptr) { + // Adjust the memory info counter. + // TODO(addaleax): Avoid the double bookkeeping we do with + // current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory + // and provide versions of our memory allocation utilities that take an + // Environment*/Isolate* parameter and call the V8 method transparently. + const int64_t new_size = size - previous_size; + manager->IncreaseAllocatedSize(new_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + new_size); + *reinterpret_cast(mem) = size; + mem += sizeof(size_t); + } else if (size == 0) { + manager->DecreaseAllocatedSize(previous_size); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(previous_size)); + } + return mem; +} + +template +void* NgLibMemoryManager::MallocImpl(size_t size, void* user_data) { + return ReallocImpl(nullptr, size, user_data); +} + +template +void NgLibMemoryManager::FreeImpl(void* ptr, void* user_data) { + if (ptr == nullptr) return; + CHECK_NULL(ReallocImpl(ptr, 0, user_data)); +} + +template +void* NgLibMemoryManager::CallocImpl(size_t nmemb, + size_t size, + void* user_data) { + size_t real_size = MultiplyWithOverflowCheck(nmemb, size); + void* mem = MallocImpl(real_size, user_data); + if (mem != nullptr) + memset(mem, 0, real_size); + return mem; +} + +template +void NgLibMemoryManager::StopTrackingMemory(void* ptr) { + size_t* original_ptr = reinterpret_cast( + static_cast(ptr) - sizeof(size_t)); + Class* manager = static_cast(this); + manager->DecreaseAllocatedSize(*original_ptr); + manager->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( + -static_cast(*original_ptr)); + *original_ptr = 0; +} + +} // namespace mem +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_MEM_INL_H_ diff --git a/src/node_mem.h b/src/node_mem.h new file mode 100644 index 00000000000000..1c55f04a313aff --- /dev/null +++ b/src/node_mem.h @@ -0,0 +1,41 @@ +#ifndef SRC_NODE_MEM_H_ +#define SRC_NODE_MEM_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include + +namespace node { +namespace mem { + +// Both ngtcp2 and nghttp2 allow custom allocators that +// follow exactly the same structure and behavior, but +// use different struct names. To allow for code re-use, +// the NgLibMemoryManager template class can be used for both. + +template +class NgLibMemoryManager { + public: + // Class needs to provide these methods: + // void CheckAllocatedSize(size_t previous_size) const; + // void IncreaseAllocatedSize(size_t size); + // void DecreaseAllocatedSize(size_t size); + // Environment* env() const; + + AllocatorStructName MakeAllocator(); + + void StopTrackingMemory(void* ptr); + + private: + static void* ReallocImpl(void* ptr, size_t size, void* user_data); + static void* MallocImpl(size_t size, void* user_data); + static void FreeImpl(void* ptr, void* user_data); + static void* CallocImpl(size_t nmemb, size_t size, void* user_data); +}; + +} // namespace mem +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_MEM_H_ From 34a9e5f6886a4637cf4e487236d857f82953608e Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 21 Sep 2019 18:57:46 +0200 Subject: [PATCH 2/6] http2: use shared memory tracking implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extensive testing done on http2 makes it easier to make sure the implementation is correct (and doesn’t diverge unnecessarily). Refs: https://github.com/nodejs/quic/pull/126 Reviewed-By: Daniel Bevenius Reviewed-By: James M Snell --- src/node_http2.cc | 108 ++++++---------------------------------------- src/node_http2.h | 11 ++++- 2 files changed, 23 insertions(+), 96 deletions(-) diff --git a/src/node_http2.cc b/src/node_http2.cc index 9421c36f3be561..97ebc3de2e1096 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_http2.h" #include "node_http2_state.h" +#include "node_mem-inl.h" #include "node_perf.h" #include "node_revert.h" #include "util-inl.h" @@ -501,101 +502,20 @@ Http2Session::Callbacks::~Callbacks() { nghttp2_session_callbacks_del(callbacks); } -// Track memory allocated by nghttp2 using a custom allocator. -class Http2Session::MemoryAllocatorInfo { - public: - explicit MemoryAllocatorInfo(Http2Session* session) - : info({ session, H2Malloc, H2Free, H2Calloc, H2Realloc }) {} - - static void* H2Malloc(size_t size, void* user_data) { - return H2Realloc(nullptr, size, user_data); - } - - static void* H2Calloc(size_t nmemb, size_t size, void* user_data) { - size_t real_size = MultiplyWithOverflowCheck(nmemb, size); - void* mem = H2Malloc(real_size, user_data); - if (mem != nullptr) - memset(mem, 0, real_size); - return mem; - } - - static void H2Free(void* ptr, void* user_data) { - if (ptr == nullptr) return; // free(null); happens quite often. - void* result = H2Realloc(ptr, 0, user_data); - CHECK_NULL(result); - } - - static void* H2Realloc(void* ptr, size_t size, void* user_data) { - Http2Session* session = static_cast(user_data); - size_t previous_size = 0; - char* original_ptr = nullptr; - - // We prepend each allocated buffer with a size_t containing the full - // size of the allocation. - if (size > 0) size += sizeof(size_t); - - if (ptr != nullptr) { - // We are free()ing or re-allocating. - original_ptr = static_cast(ptr) - sizeof(size_t); - previous_size = *reinterpret_cast(original_ptr); - // This means we called StopTracking() on this pointer before. - if (previous_size == 0) { - // Fall back to the standard Realloc() function. - char* ret = UncheckedRealloc(original_ptr, size); - if (ret != nullptr) - ret += sizeof(size_t); - return ret; - } - } - CHECK_GE(session->current_nghttp2_memory_, previous_size); - - // TODO(addaleax): Add the following, and handle NGHTTP2_ERR_NOMEM properly - // everywhere: - // - // if (size > previous_size && - // !session->IsAvailableSessionMemory(size - previous_size)) { - // return nullptr; - //} - - char* mem = UncheckedRealloc(original_ptr, size); - - if (mem != nullptr) { - // Adjust the memory info counter. - // TODO(addaleax): Avoid the double bookkeeping we do with - // current_nghttp2_memory_ + AdjustAmountOfExternalAllocatedMemory - // and provide versions of our memory allocation utilities that take an - // Environment*/Isolate* parameter and call the V8 method transparently. - const int64_t new_size = size - previous_size; - session->current_nghttp2_memory_ += new_size; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - new_size); - *reinterpret_cast(mem) = size; - mem += sizeof(size_t); - } else if (size == 0) { - session->current_nghttp2_memory_ -= previous_size; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - -static_cast(previous_size)); - } - - return mem; - } - - static void StopTracking(Http2Session* session, void* ptr) { - size_t* original_ptr = reinterpret_cast( - static_cast(ptr) - sizeof(size_t)); - session->current_nghttp2_memory_ -= *original_ptr; - session->env()->isolate()->AdjustAmountOfExternalAllocatedMemory( - -static_cast(*original_ptr)); - *original_ptr = 0; - } +void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) { + StopTrackingMemory(buf); +} - inline nghttp2_mem* operator*() { return &info; } +void Http2Session::CheckAllocatedSize(size_t previous_size) const { + CHECK_GE(current_nghttp2_memory_, previous_size); +} - nghttp2_mem info; -}; +void Http2Session::IncreaseAllocatedSize(size_t size) { + current_nghttp2_memory_ += size; +} -void Http2Session::StopTrackingRcbuf(nghttp2_rcbuf* buf) { - MemoryAllocatorInfo::StopTracking(this, buf); +void Http2Session::DecreaseAllocatedSize(size_t size) { + current_nghttp2_memory_ -= size; } Http2Session::Http2Session(Environment* env, @@ -632,14 +552,14 @@ Http2Session::Http2Session(Environment* env, nghttp2_session_server_new3 : nghttp2_session_client_new3; - MemoryAllocatorInfo allocator_info(this); + nghttp2_mem alloc_info = MakeAllocator(); // This should fail only if the system is out of memory, which // is going to cause lots of other problems anyway, or if any // of the options are out of acceptable range, which we should // be catching before it gets this far. Either way, crash if this // fails. - CHECK_EQ(fn(&session_, callbacks, this, *opts, *allocator_info), 0); + CHECK_EQ(fn(&session_, callbacks, this, *opts, &alloc_info), 0); outgoing_storage_.reserve(1024); outgoing_buffers_.reserve(32); diff --git a/src/node_http2.h b/src/node_http2.h index e828999f2b73b2..1812cba51f4793 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -8,6 +8,7 @@ #include "nghttp2/nghttp2.h" #include "node_http2_state.h" +#include "node_mem.h" #include "node_perf.h" #include "stream_base-inl.h" #include "string_bytes.h" @@ -703,7 +704,9 @@ enum SessionBitfieldFlags { kSessionHasAltsvcListeners }; -class Http2Session : public AsyncWrap, public StreamListener { +class Http2Session : public AsyncWrap, + public StreamListener, + public mem::NgLibMemoryManager { public: Http2Session(Environment* env, Local wrap, @@ -712,7 +715,6 @@ class Http2Session : public AsyncWrap, public StreamListener { class Http2Ping; class Http2Settings; - class MemoryAllocatorInfo; void EmitStatistics(); @@ -813,6 +815,11 @@ class Http2Session : public AsyncWrap, public StreamListener { void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; void OnStreamAfterWrite(WriteWrap* w, int status) override; + // Implementation for mem::NgLibMemoryManager + void CheckAllocatedSize(size_t previous_size) const; + void IncreaseAllocatedSize(size_t size); + void DecreaseAllocatedSize(size_t size); + // The JavaScript API static void New(const FunctionCallbackInfo& args); static void Consume(const FunctionCallbackInfo& args); From a5508833de3be362ff4bf3c18cb93d17a1549289 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 1 Dec 2019 01:40:55 +0100 Subject: [PATCH 3/6] http2: track nghttp2-allocated memory in heap snapshot --- src/node_http2.h | 1 + test/pummel/test-heapdump-http2.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/node_http2.h b/src/node_http2.h index 1812cba51f4793..07febd7da40a9b 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -792,6 +792,7 @@ class Http2Session : public AsyncWrap, tracker->TrackFieldWithSize("outgoing_storage", outgoing_storage_.size()); tracker->TrackFieldWithSize("pending_rst_streams", pending_rst_streams_.size() * sizeof(int32_t)); + tracker->TrackFieldWithSize("nghttp2_memory", current_nghttp2_memory_); } SET_MEMORY_INFO_NAME(Http2Session) diff --git a/test/pummel/test-heapdump-http2.js b/test/pummel/test-heapdump-http2.js index caece96d01cc72..b3d819edaef91a 100644 --- a/test/pummel/test-heapdump-http2.js +++ b/test/pummel/test-heapdump-http2.js @@ -69,6 +69,7 @@ server.listen(0, () => { { children: [ { node_name: 'Http2Session', edge_name: 'wrapped' }, + { node_name: 'Node / nghttp2_memory', edge_name: 'nghttp2_memory' }, { node_name: 'Node / streams', edge_name: 'streams' } From 11135bbd389ad4d49b2ab054fc94e771e4dd92ab Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 1 Dec 2019 01:28:52 +0100 Subject: [PATCH 4/6] deps: update uvwasi --- deps/uvwasi/include/fd_table.h | 15 +- deps/uvwasi/include/uvwasi.h | 16 +- deps/uvwasi/src/fd_table.c | 125 ++++++-- deps/uvwasi/src/uvwasi.c | 508 ++++++++++++++++++++++++--------- deps/uvwasi/src/uvwasi_alloc.h | 11 + 5 files changed, 497 insertions(+), 178 deletions(-) create mode 100644 deps/uvwasi/src/uvwasi_alloc.h diff --git a/deps/uvwasi/include/fd_table.h b/deps/uvwasi/include/fd_table.h index ef87a3ff8f93c5..42a5f0d920ff6e 100644 --- a/deps/uvwasi/include/fd_table.h +++ b/deps/uvwasi/include/fd_table.h @@ -16,6 +16,7 @@ # define PATH_MAX_BYTES (PATH_MAX) #endif +struct uvwasi_s; struct uvwasi_fd_wrap_t { uvwasi_fd_t id; @@ -27,22 +28,28 @@ struct uvwasi_fd_wrap_t { uvwasi_rights_t rights_inheriting; int preopen; int valid; + uv_mutex_t mutex; }; struct uvwasi_fd_table_t { struct uvwasi_fd_wrap_t* fds; uint32_t size; uint32_t used; + uv_rwlock_t rwlock; }; -uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_fd_table_t* table, +uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_s* uvwasi, + struct uvwasi_fd_table_t* table, uint32_t init_size); -void uvwasi_fd_table_free(struct uvwasi_fd_table_t* table); -uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_fd_table_t* table, +void uvwasi_fd_table_free(struct uvwasi_s* uvwasi, + struct uvwasi_fd_table_t* table); +uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_s* uvwasi, + struct uvwasi_fd_table_t* table, const uv_file fd, const char* path, const char* real_path); -uvwasi_errno_t uvwasi_fd_table_insert_fd(struct uvwasi_fd_table_t* table, +uvwasi_errno_t uvwasi_fd_table_insert_fd(struct uvwasi_s* uvwasi, + struct uvwasi_fd_table_t* table, const uv_file fd, const int flags, const char* path, diff --git a/deps/uvwasi/include/uvwasi.h b/deps/uvwasi/include/uvwasi.h index a7695734ac2ac6..2a4e389164357d 100644 --- a/deps/uvwasi/include/uvwasi.h +++ b/deps/uvwasi/include/uvwasi.h @@ -20,7 +20,20 @@ extern "C" { #define UVWASI_VERSION_STRING UVWASI_STRINGIFY(UVWASI_VERSION_MAJOR) "." \ UVWASI_STRINGIFY(UVWASI_VERSION_MINOR) "." \ UVWASI_STRINGIFY(UVWASI_VERSION_PATCH) +#define UVWASI_VERSION_WASI "snapshot_0" +typedef void* (*uvwasi_malloc)(size_t size, void* mem_user_data); +typedef void (*uvwasi_free)(void* ptr, void* mem_user_data); +typedef void* (*uvwasi_calloc)(size_t nmemb, size_t size, void* mem_user_data); +typedef void* (*uvwasi_realloc)(void* ptr, size_t size, void* mem_user_data); + +typedef struct uvwasi_mem_s { + void* mem_user_data; + uvwasi_malloc malloc; + uvwasi_free free; + uvwasi_calloc calloc; + uvwasi_realloc realloc; +} uvwasi_mem_t; typedef struct uvwasi_s { struct uvwasi_fd_table_t fds; @@ -32,6 +45,7 @@ typedef struct uvwasi_s { char** env; char* env_buf; size_t env_buf_size; + const uvwasi_mem_t* allocator; } uvwasi_t; typedef struct uvwasi_preopen_s { @@ -46,9 +60,9 @@ typedef struct uvwasi_options_s { size_t argc; char** argv; char** envp; + const uvwasi_mem_t* allocator; } uvwasi_options_t; - // Embedder API. uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options); void uvwasi_destroy(uvwasi_t* uvwasi); diff --git a/deps/uvwasi/src/fd_table.c b/deps/uvwasi/src/fd_table.c index 9343868074d10c..f716495e9a02b2 100644 --- a/deps/uvwasi/src/fd_table.c +++ b/deps/uvwasi/src/fd_table.c @@ -10,6 +10,7 @@ #include "fd_table.h" #include "wasi_types.h" #include "uv_mapping.h" +#include "uvwasi_alloc.h" #define UVWASI__RIGHTS_ALL (UVWASI_RIGHT_FD_DATASYNC | \ @@ -175,7 +176,8 @@ static uvwasi_errno_t uvwasi__get_type_and_rights(uv_file fd, } -static uvwasi_errno_t uvwasi__fd_table_insert(struct uvwasi_fd_table_t* table, +static uvwasi_errno_t uvwasi__fd_table_insert(uvwasi_t* uvwasi, + struct uvwasi_fd_table_t* table, uv_file fd, const char* mapped_path, const char* real_path, @@ -186,16 +188,22 @@ static uvwasi_errno_t uvwasi__fd_table_insert(struct uvwasi_fd_table_t* table, struct uvwasi_fd_wrap_t** wrap) { struct uvwasi_fd_wrap_t* entry; struct uvwasi_fd_wrap_t* new_fds; + uvwasi_errno_t err; uint32_t new_size; int index; uint32_t i; + int r; + + uv_rwlock_wrlock(&table->rwlock); /* Check that there is room for a new item. If there isn't, grow the table. */ if (table->used >= table->size) { new_size = table->size * 2; - new_fds = realloc(table->fds, new_size * sizeof(*new_fds)); - if (new_fds == NULL) - return UVWASI_ENOMEM; + new_fds = uvwasi__realloc(uvwasi, table->fds, new_size * sizeof(*new_fds)); + if (new_fds == NULL) { + err = UVWASI_ENOMEM; + goto exit; + } for (i = table->size; i < new_size; ++i) new_fds[i].valid = 0; @@ -214,11 +222,20 @@ static uvwasi_errno_t uvwasi__fd_table_insert(struct uvwasi_fd_table_t* table, } /* index should never be -1. */ - if (index == -1) - return UVWASI_ENOSPC; + if (index == -1) { + err = UVWASI_ENOSPC; + goto exit; + } } entry = &table->fds[index]; + + r = uv_mutex_init(&entry->mutex); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } + entry->id = index; entry->fd = fd; strcpy(entry->path, mapped_path); @@ -233,11 +250,15 @@ static uvwasi_errno_t uvwasi__fd_table_insert(struct uvwasi_fd_table_t* table, if (wrap != NULL) *wrap = entry; - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_rwlock_wrunlock(&table->rwlock); + return err; } -uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_fd_table_t* table, +uvwasi_errno_t uvwasi_fd_table_init(uvwasi_t* uvwasi, + struct uvwasi_fd_table_t* table, uint32_t init_size) { struct uvwasi_fd_wrap_t* wrap; uvwasi_filetype_t type; @@ -245,17 +266,27 @@ uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_fd_table_t* table, uvwasi_rights_t inheriting; uvwasi_errno_t err; uvwasi_fd_t i; + int r; /* Require an initial size of at least three to store the stdio FDs. */ if (table == NULL || init_size < 3) return UVWASI_EINVAL; + table->fds = NULL; + r = uv_rwlock_init(&table->rwlock); + if (r != 0) + return uvwasi__translate_uv_error(r); + table->used = 0; table->size = init_size; - table->fds = calloc(init_size, sizeof(struct uvwasi_fd_wrap_t)); + table->fds = uvwasi__calloc(uvwasi, + init_size, + sizeof(struct uvwasi_fd_wrap_t)); - if (table->fds == NULL) - return UVWASI_ENOMEM; + if (table->fds == NULL) { + err = UVWASI_ENOMEM; + goto error_exit; + } /* Create the stdio FDs. */ for (i = 0; i < 3; ++i) { @@ -267,7 +298,8 @@ uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_fd_table_t* table, if (err != UVWASI_ESUCCESS) goto error_exit; - err = uvwasi__fd_table_insert(table, + err = uvwasi__fd_table_insert(uvwasi, + table, i, "", "", @@ -287,23 +319,25 @@ uvwasi_errno_t uvwasi_fd_table_init(struct uvwasi_fd_table_t* table, return UVWASI_ESUCCESS; error_exit: - uvwasi_fd_table_free(table); + uvwasi_fd_table_free(uvwasi, table); return err; } -void uvwasi_fd_table_free(struct uvwasi_fd_table_t* table) { +void uvwasi_fd_table_free(uvwasi_t* uvwasi, struct uvwasi_fd_table_t* table) { if (table == NULL) return; - free(table->fds); + uvwasi__free(uvwasi, table->fds); table->fds = NULL; table->size = 0; table->used = 0; + uv_rwlock_destroy(&table->rwlock); } -uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_fd_table_t* table, +uvwasi_errno_t uvwasi_fd_table_insert_preopen(uvwasi_t* uvwasi, + struct uvwasi_fd_table_t* table, const uv_file fd, const char* path, const char* real_path) { @@ -322,7 +356,8 @@ uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_fd_table_t* table, if (type != UVWASI_FILETYPE_DIRECTORY) return UVWASI_ENOTDIR; - err = uvwasi__fd_table_insert(table, + err = uvwasi__fd_table_insert(uvwasi, + table, fd, path, real_path, @@ -338,7 +373,8 @@ uvwasi_errno_t uvwasi_fd_table_insert_preopen(struct uvwasi_fd_table_t* table, } -uvwasi_errno_t uvwasi_fd_table_insert_fd(struct uvwasi_fd_table_t* table, +uvwasi_errno_t uvwasi_fd_table_insert_fd(uvwasi_t* uvwasi, + struct uvwasi_fd_table_t* table, const uv_file fd, const int flags, const char* path, @@ -358,7 +394,8 @@ uvwasi_errno_t uvwasi_fd_table_insert_fd(struct uvwasi_fd_table_t* table, if (r != UVWASI_ESUCCESS) return r; - r = uvwasi__fd_table_insert(table, + r = uvwasi__fd_table_insert(uvwasi, + table, fd, path, path, @@ -381,42 +418,68 @@ uvwasi_errno_t uvwasi_fd_table_get(const struct uvwasi_fd_table_t* table, uvwasi_rights_t rights_base, uvwasi_rights_t rights_inheriting) { struct uvwasi_fd_wrap_t* entry; + uvwasi_errno_t err; if (table == NULL || wrap == NULL) return UVWASI_EINVAL; - if (id >= table->size) - return UVWASI_EBADF; + + uv_rwlock_rdlock((uv_rwlock_t *)&table->rwlock); + + if (id >= table->size) { + err = UVWASI_EBADF; + goto exit; + } entry = &table->fds[id]; - if (entry->valid != 1 || entry->id != id) - return UVWASI_EBADF; + if (entry->valid != 1 || entry->id != id) { + err = UVWASI_EBADF; + goto exit; + } /* Validate that the fd has the necessary rights. */ if ((~entry->rights_base & rights_base) != 0 || - (~entry->rights_inheriting & rights_inheriting) != 0) - return UVWASI_ENOTCAPABLE; + (~entry->rights_inheriting & rights_inheriting) != 0) { + err = UVWASI_ENOTCAPABLE; + goto exit; + } + uv_mutex_lock(&entry->mutex); *wrap = entry; - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_rwlock_rdunlock((uv_rwlock_t *)&table->rwlock); + return err; } uvwasi_errno_t uvwasi_fd_table_remove(struct uvwasi_fd_table_t* table, const uvwasi_fd_t id) { struct uvwasi_fd_wrap_t* entry; + uvwasi_errno_t err; if (table == NULL) return UVWASI_EINVAL; - if (id >= table->size) - return UVWASI_EBADF; + + uv_rwlock_wrlock(&table->rwlock); + + if (id >= table->size) { + err = UVWASI_EBADF; + goto exit; + } entry = &table->fds[id]; - if (entry->valid != 1 || entry->id != id) - return UVWASI_EBADF; + if (entry->valid != 1 || entry->id != id) { + err = UVWASI_EBADF; + goto exit; + } + uv_mutex_destroy(&entry->mutex); entry->valid = 0; table->used--; - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_rwlock_wrunlock(&table->rwlock); + return err; } diff --git a/deps/uvwasi/src/uvwasi.c b/deps/uvwasi/src/uvwasi.c index 39a94b6d217c52..0f7c4c5d3aa0ef 100644 --- a/deps/uvwasi/src/uvwasi.c +++ b/deps/uvwasi/src/uvwasi.c @@ -19,26 +19,69 @@ #define UVWASI__READDIR_NUM_ENTRIES 1 #include "uvwasi.h" +#include "uvwasi_alloc.h" #include "uv.h" #include "uv_mapping.h" #include "fd_table.h" #include "clocks.h" +static void* default_malloc(size_t size, void* mem_user_data) { + return malloc(size); +} + +static void default_free(void* ptr, void* mem_user_data) { + free(ptr); +} + +static void* default_calloc(size_t nmemb, size_t size, void* mem_user_data) { + return calloc(nmemb, size); +} + +static void* default_realloc(void* ptr, size_t size, void* mem_user_data) { + return realloc(ptr, size); +} + +void* uvwasi__malloc(const uvwasi_t* uvwasi, size_t size) { + return uvwasi->allocator->malloc(size, uvwasi->allocator->mem_user_data); +} + +void uvwasi__free(const uvwasi_t* uvwasi, void* ptr) { + uvwasi->allocator->free(ptr, uvwasi->allocator->mem_user_data); +} + +void* uvwasi__calloc(const uvwasi_t* uvwasi, size_t nmemb, size_t size) { + return uvwasi->allocator->calloc(nmemb, + size, + uvwasi->allocator->mem_user_data); +} + +void* uvwasi__realloc(const uvwasi_t* uvwasi, void* ptr, size_t size) { + return uvwasi->allocator->realloc(ptr, + size, + uvwasi->allocator->mem_user_data); +} + +static const uvwasi_mem_t default_allocator = { + NULL, + default_malloc, + default_free, + default_calloc, + default_realloc, +}; + static int uvwasi__is_absolute_path(const char* path, size_t path_len) { - /* TODO(cjihrig): Add a Windows implementation. */ - return path != NULL && path_len > 0 && path[0] == SLASH; + /* It's expected that only Unix style paths will be generated by WASI. */ + return path != NULL && path_len > 0 && path[0] == '/'; } -static uvwasi_errno_t uvwasi__resolve_path(const struct uvwasi_fd_wrap_t* fd, +static uvwasi_errno_t uvwasi__resolve_path(const uvwasi_t* uvwasi, + const struct uvwasi_fd_wrap_t* fd, const char* path, size_t path_len, char* resolved_path, uvwasi_lookupflags_t flags) { - /* TODO(cjihrig): path_len is treated as a size. Need to verify if path_len is - really a string length or a size. Also need to verify if it is null - terminated. */ uv_fs_t realpath_req; uvwasi_errno_t err; char* abs_path; @@ -58,7 +101,7 @@ static uvwasi_errno_t uvwasi__resolve_path(const struct uvwasi_fd_wrap_t* fd, if (1 == input_is_absolute) { /* TODO(cjihrig): Revisit this. Copying is probably not necessary here. */ abs_size = path_len; - abs_path = malloc(abs_size); + abs_path = uvwasi__malloc(uvwasi, abs_size); if (abs_path == NULL) { err = UVWASI_ENOMEM; goto exit; @@ -68,7 +111,7 @@ static uvwasi_errno_t uvwasi__resolve_path(const struct uvwasi_fd_wrap_t* fd, } else { /* Resolve the relative path to fd's real path. */ abs_size = path_len + strlen(fd->real_path) + 2; - abs_path = malloc(abs_size); + abs_path = uvwasi__malloc(uvwasi, abs_size); if (abs_path == NULL) { err = UVWASI_ENOMEM; goto exit; @@ -146,7 +189,7 @@ static uvwasi_errno_t uvwasi__resolve_path(const struct uvwasi_fd_wrap_t* fd, } exit: - free(abs_path); + uvwasi__free(uvwasi, abs_path); return err; } @@ -185,13 +228,17 @@ static uvwasi_errno_t uvwasi__lseek(uv_file fd, } -static uvwasi_errno_t uvwasi__setup_iovs(uv_buf_t** buffers, +static uvwasi_errno_t uvwasi__setup_iovs(const uvwasi_t* uvwasi, + uv_buf_t** buffers, const uvwasi_iovec_t* iovs, size_t iovs_len) { uv_buf_t* bufs; size_t i; - bufs = malloc(iovs_len * sizeof(*bufs)); + if ((iovs_len * sizeof(*bufs)) / (sizeof(*bufs)) != iovs_len) + return UVWASI_ENOMEM; + + bufs = uvwasi__malloc(uvwasi, iovs_len * sizeof(*bufs)); if (bufs == NULL) return UVWASI_ENOMEM; @@ -203,13 +250,17 @@ static uvwasi_errno_t uvwasi__setup_iovs(uv_buf_t** buffers, } -static uvwasi_errno_t uvwasi__setup_ciovs(uv_buf_t** buffers, +static uvwasi_errno_t uvwasi__setup_ciovs(const uvwasi_t* uvwasi, + uv_buf_t** buffers, const uvwasi_ciovec_t* iovs, size_t iovs_len) { uv_buf_t* bufs; size_t i; - bufs = malloc(iovs_len * sizeof(*bufs)); + if ((iovs_len * sizeof(*bufs)) / (sizeof(*bufs)) != iovs_len) + return UVWASI_ENOMEM; + + bufs = uvwasi__malloc(uvwasi, iovs_len * sizeof(*bufs)); if (bufs == NULL) return UVWASI_ENOMEM; @@ -236,6 +287,10 @@ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options) { if (uvwasi == NULL || options == NULL || options->fd_table_size == 0) return UVWASI_EINVAL; + uvwasi->allocator = options->allocator; + if (uvwasi->allocator == NULL) + uvwasi->allocator = &default_allocator; + uvwasi->argv_buf = NULL; uvwasi->argv = NULL; uvwasi->env_buf = NULL; @@ -250,13 +305,13 @@ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options) { uvwasi->argv_buf_size = args_size; if (args_size > 0) { - uvwasi->argv_buf = malloc(args_size); + uvwasi->argv_buf = uvwasi__malloc(uvwasi, args_size); if (uvwasi->argv_buf == NULL) { err = UVWASI_ENOMEM; goto exit; } - uvwasi->argv = calloc(options->argc, sizeof(char*)); + uvwasi->argv = uvwasi__calloc(uvwasi, options->argc, sizeof(char*)); if (uvwasi->argv == NULL) { err = UVWASI_ENOMEM; goto exit; @@ -284,13 +339,13 @@ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options) { uvwasi->env_buf_size = env_buf_size; if (env_buf_size > 0) { - uvwasi->env_buf = malloc(env_buf_size); + uvwasi->env_buf = uvwasi__malloc(uvwasi, env_buf_size); if (uvwasi->env_buf == NULL) { err = UVWASI_ENOMEM; goto exit; } - uvwasi->env = calloc(env_count, sizeof(char*)); + uvwasi->env = uvwasi__calloc(uvwasi, env_count, sizeof(char*)); if (uvwasi->env == NULL) { err = UVWASI_ENOMEM; goto exit; @@ -313,7 +368,7 @@ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options) { } } - err = uvwasi_fd_table_init(&uvwasi->fds, options->fd_table_size); + err = uvwasi_fd_table_init(uvwasi, &uvwasi->fds, options->fd_table_size); if (err != UVWASI_ESUCCESS) goto exit; @@ -336,7 +391,8 @@ uvwasi_errno_t uvwasi_init(uvwasi_t* uvwasi, uvwasi_options_t* options) { goto exit; } - err = uvwasi_fd_table_insert_preopen(&uvwasi->fds, + err = uvwasi_fd_table_insert_preopen(uvwasi, + &uvwasi->fds, open_req.result, options->preopens[i].mapped_path, realpath_req.ptr); @@ -359,11 +415,11 @@ void uvwasi_destroy(uvwasi_t* uvwasi) { if (uvwasi == NULL) return; - uvwasi_fd_table_free(&uvwasi->fds); - free(uvwasi->argv_buf); - free(uvwasi->argv); - free(uvwasi->env_buf); - free(uvwasi->env); + uvwasi_fd_table_free(uvwasi, &uvwasi->fds); + uvwasi__free(uvwasi, uvwasi->argv_buf); + uvwasi__free(uvwasi, uvwasi->argv); + uvwasi__free(uvwasi, uvwasi->env_buf); + uvwasi__free(uvwasi, uvwasi->env); uvwasi->argv_buf = NULL; uvwasi->argv = NULL; uvwasi->env_buf = NULL; @@ -385,6 +441,7 @@ uvwasi_errno_t uvwasi_embedder_remap_fd(uvwasi_t* uvwasi, return err; wrap->fd = new_host_fd; + uv_mutex_unlock(&wrap->mutex); return UVWASI_ESUCCESS; } @@ -543,12 +600,15 @@ uvwasi_errno_t uvwasi_fd_advise(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; + err = UVWASI_ESUCCESS; + #ifdef POSIX_FADV_NORMAL r = posix_fadvise(wrap->fd, offset, len, mapped_advice); if (r != 0) - return uvwasi__translate_uv_error(uv_translate_sys_error(r)); + err = uvwasi__translate_uv_error(uv_translate_sys_error(r)); #endif /* POSIX_FADV_NORMAL */ - return UVWASI_ESUCCESS; + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -579,23 +639,32 @@ uvwasi_errno_t uvwasi_fd_allocate(uvwasi_t* uvwasi, race condition prone combination of fstat() + ftruncate(). */ #if defined(__POSIX__) r = posix_fallocate(wrap->fd, offset, len); - if (r != 0) - return uvwasi__translate_uv_error(uv_translate_sys_error(r)); + if (r != 0) { + err = uvwasi__translate_uv_error(uv_translate_sys_error(r)); + goto exit; + } #else r = uv_fs_fstat(NULL, &req, wrap->fd, NULL); st_size = req.statbuf.st_size; uv_fs_req_cleanup(&req); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } if (st_size < offset + len) { r = uv_fs_ftruncate(NULL, &req, wrap->fd, offset + len, NULL); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } } #endif /* __POSIX__ */ - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -613,6 +682,7 @@ uvwasi_errno_t uvwasi_fd_close(uvwasi_t* uvwasi, uvwasi_fd_t fd) { return err; r = uv_fs_close(NULL, &req, wrap->fd, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -640,6 +710,7 @@ uvwasi_errno_t uvwasi_fd_datasync(uvwasi_t* uvwasi, uvwasi_fd_t fd) { return err; r = uv_fs_fdatasync(NULL, &req, wrap->fd, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -672,11 +743,15 @@ uvwasi_errno_t uvwasi_fd_fdstat_get(uvwasi_t* uvwasi, buf->fs_flags = 0; /* TODO(cjihrig): Missing Windows support. */ #else r = fcntl(wrap->fd, F_GETFL); - if (r < 0) - return uvwasi__translate_uv_error(uv_translate_sys_error(errno)); + if (r < 0) { + err = uvwasi__translate_uv_error(uv_translate_sys_error(errno)); + uv_mutex_unlock(&wrap->mutex); + return err; + } buf->fs_flags = r; #endif /* _WIN32 */ + uv_mutex_unlock(&wrap->mutex); return UVWASI_ESUCCESS; } @@ -731,9 +806,12 @@ uvwasi_errno_t uvwasi_fd_fdstat_set_flags(uvwasi_t* uvwasi, r = fcntl(wrap->fd, F_SETFL, mapped_flags); if (r < 0) - return uvwasi__translate_uv_error(uv_translate_sys_error(errno)); + err = uvwasi__translate_uv_error(uv_translate_sys_error(errno)); + else + err = UVWASI_ESUCCESS; - return UVWASI_ESUCCESS; + uv_mutex_unlock(&wrap->mutex); + return err; #endif /* _WIN32 */ } @@ -754,16 +832,23 @@ uvwasi_errno_t uvwasi_fd_fdstat_set_rights(uvwasi_t* uvwasi, return err; /* Check for attempts to add new permissions. */ - if ((fs_rights_base | wrap->rights_base) > wrap->rights_base) - return UVWASI_ENOTCAPABLE; + if ((fs_rights_base | wrap->rights_base) > wrap->rights_base) { + err = UVWASI_ENOTCAPABLE; + goto exit; + } + if ((fs_rights_inheriting | wrap->rights_inheriting) > wrap->rights_inheriting) { - return UVWASI_ENOTCAPABLE; + err = UVWASI_ENOTCAPABLE; + goto exit; } wrap->rights_base = fs_rights_base; wrap->rights_inheriting = fs_rights_inheriting; - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -788,14 +873,16 @@ uvwasi_errno_t uvwasi_fd_filestat_get(uvwasi_t* uvwasi, r = uv_fs_fstat(NULL, &req, wrap->fd, NULL); if (r != 0) { - uv_fs_req_cleanup(&req); - return uvwasi__translate_uv_error(r); + err = uvwasi__translate_uv_error(r); + goto exit; } uvwasi__stat_to_filestat(&req.statbuf, buf); + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); - - return UVWASI_ESUCCESS; + return err; } @@ -820,6 +907,7 @@ uvwasi_errno_t uvwasi_fd_filestat_set_size(uvwasi_t* uvwasi, return err; r = uv_fs_ftruncate(NULL, &req, wrap->fd, st_size, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -858,6 +946,7 @@ uvwasi_errno_t uvwasi_fd_filestat_set_times(uvwasi_t* uvwasi, /* TODO(cjihrig): st_atim and st_mtim should not be unconditionally passed. */ r = uv_fs_futime(NULL, &req, wrap->fd, st_atim, st_mtim, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -891,14 +980,17 @@ uvwasi_errno_t uvwasi_fd_pread(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__setup_iovs(&bufs, iovs, iovs_len); - if (err != UVWASI_ESUCCESS) + err = uvwasi__setup_iovs(uvwasi, &bufs, iovs, iovs_len); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_read(NULL, &req, wrap->fd, bufs, iovs_len, offset, NULL); + uv_mutex_unlock(&wrap->mutex); uvread = req.result; uv_fs_req_cleanup(&req); - free(bufs); + uvwasi__free(uvwasi, bufs); if (r < 0) return uvwasi__translate_uv_error(r); @@ -920,12 +1012,17 @@ uvwasi_errno_t uvwasi_fd_prestat_get(uvwasi_t* uvwasi, err = uvwasi_fd_table_get(&uvwasi->fds, fd, &wrap, 0, 0); if (err != UVWASI_ESUCCESS) return err; - if (wrap->preopen != 1) - return UVWASI_EINVAL; + if (wrap->preopen != 1) { + err = UVWASI_EINVAL; + goto exit; + } buf->pr_type = UVWASI_PREOPENTYPE_DIR; buf->u.dir.pr_name_len = strlen(wrap->path) + 1; - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -943,15 +1040,22 @@ uvwasi_errno_t uvwasi_fd_prestat_dir_name(uvwasi_t* uvwasi, err = uvwasi_fd_table_get(&uvwasi->fds, fd, &wrap, 0, 0); if (err != UVWASI_ESUCCESS) return err; - if (wrap->preopen != 1) - return UVWASI_EBADF; + if (wrap->preopen != 1) { + err = UVWASI_EBADF; + goto exit; + } size = strlen(wrap->path) + 1; - if (size > path_len) - return UVWASI_ENOBUFS; + if (size > path_len) { + err = UVWASI_ENOBUFS; + goto exit; + } memcpy(path, wrap->path, size); - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -979,14 +1083,17 @@ uvwasi_errno_t uvwasi_fd_pwrite(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__setup_ciovs(&bufs, iovs, iovs_len); - if (err != UVWASI_ESUCCESS) + err = uvwasi__setup_ciovs(uvwasi, &bufs, iovs, iovs_len); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_write(NULL, &req, wrap->fd, bufs, iovs_len, offset, NULL); + uv_mutex_unlock(&wrap->mutex); uvwritten = req.result; uv_fs_req_cleanup(&req); - free(bufs); + uvwasi__free(uvwasi, bufs); if (r < 0) return uvwasi__translate_uv_error(r); @@ -1015,14 +1122,17 @@ uvwasi_errno_t uvwasi_fd_read(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__setup_iovs(&bufs, iovs, iovs_len); - if (err != UVWASI_ESUCCESS) + err = uvwasi__setup_iovs(uvwasi, &bufs, iovs, iovs_len); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_read(NULL, &req, wrap->fd, bufs, iovs_len, -1, NULL); + uv_mutex_unlock(&wrap->mutex); uvread = req.result; uv_fs_req_cleanup(&req); - free(bufs); + uvwasi__free(uvwasi, bufs); if (r < 0) return uvwasi__translate_uv_error(r); @@ -1066,8 +1176,10 @@ uvwasi_errno_t uvwasi_fd_readdir(uvwasi_t* uvwasi, /* Open the directory. */ r = uv_fs_opendir(NULL, &req, wrap->real_path, NULL); - if (r != 0) + if (r != 0) { + uv_mutex_unlock(&wrap->mutex); return uvwasi__translate_uv_error(r); + } /* Setup for reading the directory. */ dir = req.ptr; @@ -1161,6 +1273,7 @@ uvwasi_errno_t uvwasi_fd_readdir(uvwasi_t* uvwasi, exit: /* Close the directory. */ r = uv_fs_closedir(NULL, &req, dir, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) return uvwasi__translate_uv_error(r); @@ -1181,22 +1294,31 @@ uvwasi_errno_t uvwasi_fd_renumber(uvwasi_t* uvwasi, if (uvwasi == NULL) return UVWASI_EINVAL; + if (from == to) + return UVWASI_ESUCCESS; + err = uvwasi_fd_table_get(&uvwasi->fds, from, &from_wrap, 0, 0); if (err != UVWASI_ESUCCESS) return err; err = uvwasi_fd_table_get(&uvwasi->fds, to, &to_wrap, 0, 0); - if (err != UVWASI_ESUCCESS) + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&from_wrap->mutex); return err; + } r = uv_fs_close(NULL, &req, to_wrap->fd, NULL); uv_fs_req_cleanup(&req); - if (r != 0) + if (r != 0) { + uv_mutex_unlock(&from_wrap->mutex); + uv_mutex_unlock(&to_wrap->mutex); return uvwasi__translate_uv_error(r); + } memcpy(to_wrap, from_wrap, sizeof(*to_wrap)); to_wrap->id = to; - + uv_mutex_unlock(&from_wrap->mutex); + uv_mutex_unlock(&to_wrap->mutex); return uvwasi_fd_table_remove(&uvwasi->fds, from); } @@ -1216,7 +1338,9 @@ uvwasi_errno_t uvwasi_fd_seek(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - return uvwasi__lseek(wrap->fd, offset, whence, newoffset); + err = uvwasi__lseek(wrap->fd, offset, whence, newoffset); + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -1238,6 +1362,7 @@ uvwasi_errno_t uvwasi_fd_sync(uvwasi_t* uvwasi, uvwasi_fd_t fd) { return err; r = uv_fs_fsync(NULL, &req, wrap->fd, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -1260,7 +1385,9 @@ uvwasi_errno_t uvwasi_fd_tell(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - return uvwasi__lseek(wrap->fd, 0, UVWASI_WHENCE_CUR, offset); + err = uvwasi__lseek(wrap->fd, 0, UVWASI_WHENCE_CUR, offset); + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -1283,14 +1410,17 @@ uvwasi_errno_t uvwasi_fd_write(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__setup_ciovs(&bufs, iovs, iovs_len); - if (err != UVWASI_ESUCCESS) + err = uvwasi__setup_ciovs(uvwasi, &bufs, iovs, iovs_len); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_write(NULL, &req, wrap->fd, bufs, iovs_len, -1, NULL); + uv_mutex_unlock(&wrap->mutex); uvwritten = req.result; uv_fs_req_cleanup(&req); - free(bufs); + uvwasi__free(uvwasi, bufs); if (r < 0) return uvwasi__translate_uv_error(r); @@ -1321,17 +1451,22 @@ uvwasi_errno_t uvwasi_path_create_directory(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, 0); + err = uvwasi__resolve_path(uvwasi, wrap, path, path_len, resolved_path, 0); if (err != UVWASI_ESUCCESS) - return err; + goto exit; r = uv_fs_mkdir(NULL, &req, resolved_path, 0777, NULL); uv_fs_req_cleanup(&req); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -1358,20 +1493,28 @@ uvwasi_errno_t uvwasi_path_filestat_get(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, flags); + err = uvwasi__resolve_path(uvwasi, + wrap, + path, + path_len, + resolved_path, + flags); if (err != UVWASI_ESUCCESS) - return err; + goto exit; r = uv_fs_stat(NULL, &req, resolved_path, NULL); if (r != 0) { uv_fs_req_cleanup(&req); - return uvwasi__translate_uv_error(r); + err = uvwasi__translate_uv_error(r); + goto exit; } uvwasi__stat_to_filestat(&req.statbuf, buf); uv_fs_req_cleanup(&req); - - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -1406,18 +1549,28 @@ uvwasi_errno_t uvwasi_path_filestat_set_times(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, flags); + err = uvwasi__resolve_path(uvwasi, + wrap, + path, + path_len, + resolved_path, + flags); if (err != UVWASI_ESUCCESS) - return err; + goto exit; /* TODO(cjihrig): st_atim and st_mtim should not be unconditionally passed. */ r = uv_fs_utime(NULL, &req, resolved_path, st_atim, st_mtim, NULL); uv_fs_req_cleanup(&req); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&wrap->mutex); + return err; } @@ -1440,44 +1593,68 @@ uvwasi_errno_t uvwasi_path_link(uvwasi_t* uvwasi, if (uvwasi == NULL || old_path == NULL || new_path == NULL) return UVWASI_EINVAL; - err = uvwasi_fd_table_get(&uvwasi->fds, - old_fd, - &old_wrap, - UVWASI_RIGHT_PATH_LINK_SOURCE, - 0); - if (err != UVWASI_ESUCCESS) - return err; + if (old_fd == new_fd) { + err = uvwasi_fd_table_get(&uvwasi->fds, + old_fd, + &old_wrap, + UVWASI_RIGHT_PATH_LINK_SOURCE | + UVWASI_RIGHT_PATH_LINK_TARGET, + 0); + if (err != UVWASI_ESUCCESS) + return err; - err = uvwasi_fd_table_get(&uvwasi->fds, - new_fd, - &new_wrap, - UVWASI_RIGHT_PATH_LINK_TARGET, - 0); - if (err != UVWASI_ESUCCESS) - return err; + new_wrap = old_wrap; + } else { + err = uvwasi_fd_table_get(&uvwasi->fds, + old_fd, + &old_wrap, + UVWASI_RIGHT_PATH_LINK_SOURCE, + 0); + if (err != UVWASI_ESUCCESS) + return err; + + err = uvwasi_fd_table_get(&uvwasi->fds, + new_fd, + &new_wrap, + UVWASI_RIGHT_PATH_LINK_TARGET, + 0); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&old_wrap->mutex); + return err; + } + } - err = uvwasi__resolve_path(old_wrap, + err = uvwasi__resolve_path(uvwasi, + old_wrap, old_path, old_path_len, resolved_old_path, old_flags); if (err != UVWASI_ESUCCESS) - return err; + goto exit; - err = uvwasi__resolve_path(new_wrap, + err = uvwasi__resolve_path(uvwasi, + new_wrap, new_path, new_path_len, resolved_new_path, 0); if (err != UVWASI_ESUCCESS) - return err; + goto exit; r = uv_fs_link(NULL, &req, resolved_old_path, resolved_new_path, NULL); uv_fs_req_cleanup(&req); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&old_wrap->mutex); + if (old_fd != new_fd) + uv_mutex_unlock(&new_wrap->mutex); + return err; } @@ -1560,39 +1737,49 @@ uvwasi_errno_t uvwasi_path_open(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(dirfd_wrap, + err = uvwasi__resolve_path(uvwasi, + dirfd_wrap, path, path_len, resolved_path, dirflags); - if (err != UVWASI_ESUCCESS) + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&dirfd_wrap->mutex); return err; + } r = uv_fs_open(NULL, &req, resolved_path, flags, 0666, NULL); uv_fs_req_cleanup(&req); - if (r < 0) + if (r < 0) { + uv_mutex_unlock(&dirfd_wrap->mutex); return uvwasi__translate_uv_error(r); + } - err = uvwasi_fd_table_insert_fd(&uvwasi->fds, + err = uvwasi_fd_table_insert_fd(uvwasi, + &uvwasi->fds, r, flags, resolved_path, fs_rights_base, fs_rights_inheriting, &wrap); - if (err != UVWASI_ESUCCESS) + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&dirfd_wrap->mutex); goto close_file_and_error_exit; + } /* Not all platforms support UV_FS_O_DIRECTORY, so enforce it here as well. */ if ((o_flags & UVWASI_O_DIRECTORY) != 0 && wrap.type != UVWASI_FILETYPE_DIRECTORY) { + uv_mutex_unlock(&dirfd_wrap->mutex); uvwasi_fd_table_remove(&uvwasi->fds, wrap.id); err = UVWASI_ENOTDIR; goto close_file_and_error_exit; } *fd = wrap.id; + uv_mutex_unlock(&dirfd_wrap->mutex); return UVWASI_ESUCCESS; close_file_and_error_exit: @@ -1627,11 +1814,14 @@ uvwasi_errno_t uvwasi_path_readlink(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, 0); - if (err != UVWASI_ESUCCESS) + err = uvwasi__resolve_path(uvwasi, wrap, path, path_len, resolved_path, 0); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_readlink(NULL, &req, resolved_path, NULL); + uv_mutex_unlock(&wrap->mutex); if (r != 0) { uv_fs_req_cleanup(&req); return uvwasi__translate_uv_error(r); @@ -1672,11 +1862,14 @@ uvwasi_errno_t uvwasi_path_remove_directory(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, 0); - if (err != UVWASI_ESUCCESS) + err = uvwasi__resolve_path(uvwasi, wrap, path, path_len, resolved_path, 0); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_rmdir(NULL, &req, resolved_path, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) @@ -1704,44 +1897,68 @@ uvwasi_errno_t uvwasi_path_rename(uvwasi_t* uvwasi, if (uvwasi == NULL || old_path == NULL || new_path == NULL) return UVWASI_EINVAL; - err = uvwasi_fd_table_get(&uvwasi->fds, - old_fd, - &old_wrap, - UVWASI_RIGHT_PATH_RENAME_SOURCE, - 0); - if (err != UVWASI_ESUCCESS) - return err; - - err = uvwasi_fd_table_get(&uvwasi->fds, - new_fd, - &new_wrap, - UVWASI_RIGHT_PATH_RENAME_TARGET, - 0); - if (err != UVWASI_ESUCCESS) - return err; + if (old_fd == new_fd) { + err = uvwasi_fd_table_get(&uvwasi->fds, + old_fd, + &old_wrap, + UVWASI_RIGHT_PATH_RENAME_SOURCE | + UVWASI_RIGHT_PATH_RENAME_TARGET, + 0); + if (err != UVWASI_ESUCCESS) + return err; + new_wrap = old_wrap; + } else { + err = uvwasi_fd_table_get(&uvwasi->fds, + old_fd, + &old_wrap, + UVWASI_RIGHT_PATH_RENAME_SOURCE, + 0); + if (err != UVWASI_ESUCCESS) + return err; + + err = uvwasi_fd_table_get(&uvwasi->fds, + new_fd, + &new_wrap, + UVWASI_RIGHT_PATH_RENAME_TARGET, + 0); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&old_wrap->mutex); + return err; + } + } - err = uvwasi__resolve_path(old_wrap, + err = uvwasi__resolve_path(uvwasi, + old_wrap, old_path, old_path_len, resolved_old_path, 0); if (err != UVWASI_ESUCCESS) - return err; + goto exit; - err = uvwasi__resolve_path(new_wrap, + err = uvwasi__resolve_path(uvwasi, + new_wrap, new_path, new_path_len, resolved_new_path, 0); if (err != UVWASI_ESUCCESS) - return err; + goto exit; r = uv_fs_rename(NULL, &req, resolved_old_path, resolved_new_path, NULL); uv_fs_req_cleanup(&req); - if (r != 0) - return uvwasi__translate_uv_error(r); + if (r != 0) { + err = uvwasi__translate_uv_error(r); + goto exit; + } - return UVWASI_ESUCCESS; + err = UVWASI_ESUCCESS; +exit: + uv_mutex_unlock(&old_wrap->mutex); + if (old_fd != new_fd) + uv_mutex_unlock(&new_wrap->mutex); + + return err; } @@ -1768,16 +1985,20 @@ uvwasi_errno_t uvwasi_path_symlink(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, + err = uvwasi__resolve_path(uvwasi, + wrap, new_path, new_path_len, resolved_new_path, 0); - if (err != UVWASI_ESUCCESS) + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } /* Windows support may require setting the flags option. */ r = uv_fs_symlink(NULL, &req, old_path, resolved_new_path, 0, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) return uvwasi__translate_uv_error(r); @@ -1807,11 +2028,14 @@ uvwasi_errno_t uvwasi_path_unlink_file(uvwasi_t* uvwasi, if (err != UVWASI_ESUCCESS) return err; - err = uvwasi__resolve_path(wrap, path, path_len, resolved_path, 0); - if (err != UVWASI_ESUCCESS) + err = uvwasi__resolve_path(uvwasi, wrap, path, path_len, resolved_path, 0); + if (err != UVWASI_ESUCCESS) { + uv_mutex_unlock(&wrap->mutex); return err; + } r = uv_fs_unlink(NULL, &req, resolved_path, NULL); + uv_mutex_unlock(&wrap->mutex); uv_fs_req_cleanup(&req); if (r != 0) diff --git a/deps/uvwasi/src/uvwasi_alloc.h b/deps/uvwasi/src/uvwasi_alloc.h new file mode 100644 index 00000000000000..1486f313911b01 --- /dev/null +++ b/deps/uvwasi/src/uvwasi_alloc.h @@ -0,0 +1,11 @@ +#ifndef __UVWASI_ALLOC_H__ +#define __UVWASI_ALLOC_H__ + +#include "uvwasi.h" + +void* uvwasi__malloc(const uvwasi_t* uvwasi, size_t size); +void uvwasi__free(const uvwasi_t* uvwasi, void* ptr); +void* uvwasi__calloc(const uvwasi_t* uvwasi, size_t nmemb, size_t size); +void* uvwasi__realloc(const uvwasi_t* uvwasi, void* ptr, size_t size); + +#endif From 05d809eca85a49933ae72231f15291817a3e9c4e Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 1 Dec 2019 01:45:48 +0100 Subject: [PATCH 5/6] wasi: use memory-tracking allocator This: - Protects against memory leaks in uvwasi. - Allows tracking the allocated memory in heap dumps. --- src/node_wasi.cc | 21 +++++++++++++++++++++ src/node_wasi.h | 17 +++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 8de7f1fc1cae68..d079b760157c5c 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -1,6 +1,8 @@ #include "env-inl.h" #include "base_object-inl.h" #include "debug_utils.h" +#include "memory_tracker-inl.h" +#include "node_mem-inl.h" #include "util-inl.h" #include "node.h" #include "uv.h" @@ -85,14 +87,33 @@ WASI::WASI(Environment* env, Local object, uvwasi_options_t* options) : BaseObject(env, object) { MakeWeak(); + alloc_info_ = MakeAllocator(); + options->allocator = &alloc_info_; CHECK_EQ(uvwasi_init(&uvw_, options), UVWASI_ESUCCESS); } WASI::~WASI() { uvwasi_destroy(&uvw_); + CHECK_EQ(current_uvwasi_memory_, 0); } +void WASI::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("memory", memory_); + tracker->TrackFieldWithSize("uvwasi_memory", current_uvwasi_memory_); +} + +void WASI::CheckAllocatedSize(size_t previous_size) const { + CHECK_GE(current_uvwasi_memory_, previous_size); +} + +void WASI::IncreaseAllocatedSize(size_t size) { + current_uvwasi_memory_ += size; +} + +void WASI::DecreaseAllocatedSize(size_t size) { + current_uvwasi_memory_ -= size; +} void WASI::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); diff --git a/src/node_wasi.h b/src/node_wasi.h index ca726e48a42a47..7a0be60aa645a7 100644 --- a/src/node_wasi.h +++ b/src/node_wasi.h @@ -4,24 +4,22 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "base_object.h" -#include "memory_tracker-inl.h" +#include "node_mem.h" #include "uvwasi.h" namespace node { namespace wasi { -class WASI : public BaseObject { +class WASI : public BaseObject, + public mem::NgLibMemoryManager { public: WASI(Environment* env, v8::Local object, uvwasi_options_t* options); static void New(const v8::FunctionCallbackInfo& args); - void MemoryInfo(MemoryTracker* tracker) const override { - /* TODO(cjihrig): Get memory consumption from uvwasi. */ - tracker->TrackField("memory", memory_); - } + void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(WASI) SET_SELF_SIZE(WASI) @@ -79,6 +77,11 @@ class WASI : public BaseObject { static void _SetMemory(const v8::FunctionCallbackInfo& args); + // Implementation for mem::NgLibMemoryManager + void CheckAllocatedSize(size_t previous_size) const; + void IncreaseAllocatedSize(size_t size); + void DecreaseAllocatedSize(size_t size); + private: ~WASI() override; inline void readUInt8(char* memory, uint8_t* value, uint32_t offset); @@ -92,6 +95,8 @@ class WASI : public BaseObject { uvwasi_errno_t backingStore(char** store, size_t* byte_length); uvwasi_t uvw_; v8::Global memory_; + uvwasi_mem_t alloc_info_; + size_t current_uvwasi_memory_ = 0; }; From 8bf83d39f148bd48df739345a9b86cf4fa6ce8ec Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 4 Dec 2019 14:30:38 +0100 Subject: [PATCH 6/6] fixup! src: port memory-tracking allocator from QUIC repo Co-Authored-By: Jiawen Geng --- src/node_mem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_mem.h b/src/node_mem.h index 1c55f04a313aff..0d3388ad4766bb 100644 --- a/src/node_mem.h +++ b/src/node_mem.h @@ -3,7 +3,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include +#include namespace node { namespace mem {