From 8ab02314baa4bf6fd1e3769b7222943a7084db28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?John=20H=C3=B6gberg?= <john@erlang.org>
Date: Mon, 9 Oct 2017 08:59:32 +0200
Subject: Add zlib:set_controlling_process/2

---
 erts/doc/src/zlib.xml                |  14 ++++++++
 erts/emulator/nifs/common/zlib_nif.c |  64 +++++++++++++++++++++++++++++------
 erts/preloaded/ebin/zlib.beam        | Bin 19492 -> 19784 bytes
 erts/preloaded/src/zlib.erl          |  11 +++++-
 4 files changed, 77 insertions(+), 12 deletions(-)

(limited to 'erts')

diff --git a/erts/doc/src/zlib.xml b/erts/doc/src/zlib.xml
index f5cc1b1e64..6f4c42da27 100644
--- a/erts/doc/src/zlib.xml
+++ b/erts/doc/src/zlib.xml
@@ -71,6 +71,11 @@ list_to_binary([Compressed|Last])</pre>
       called prior to a call to
       <seealso marker="#inflate/2"><c>inflate/2</c></seealso>.
       </item>
+      <tag><c>not_on_controlling_process</c></tag>
+      <item>The stream was used by a process that doesn't control it. Use
+      <seealso marker="#set_controlling_process/2">
+      <c>set_controlling_process/2</c></seealso> if you need to transfer
+      a stream to a different process.</item>
       <tag><c>data_error</c></tag>
       <item>The data contains errors.
       </item>
@@ -739,6 +744,15 @@ loop(Z, Handler, {finished, Output}) ->
       </desc>
     </func>
 
+    <func>
+      <name name="set_controlling_process" arity="2"/>
+      <fsummary>Transfers ownership of a zlib stream.</fsummary>
+      <desc>
+        <p>Changes the controlling process of <c><anno>Z</anno></c> to
+           <c><anno>Pid</anno></c>, which must be a local process.</p>
+      </desc>
+    </func>
+
     <func>
       <name name="uncompress" arity="1"/>
       <fsummary>Uncompress data with standard zlib functionality.</fsummary>
diff --git a/erts/emulator/nifs/common/zlib_nif.c b/erts/emulator/nifs/common/zlib_nif.c
index a9c5b05e47..fa29b4fb71 100644
--- a/erts/emulator/nifs/common/zlib_nif.c
+++ b/erts/emulator/nifs/common/zlib_nif.c
@@ -106,6 +106,7 @@ typedef struct {
     int inflateChunk_buffer_size;
 
     ErlNifPid controlling_process;
+    ErlNifMutex *controller_lock;
 
     ErlNifIOQueue *input_queue;
 
@@ -117,6 +118,8 @@ typedef struct {
 
 static ERL_NIF_TERM zlib_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
 static ERL_NIF_TERM zlib_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM zlib_set_controller(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
 static ERL_NIF_TERM zlib_deflateInit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
 static ERL_NIF_TERM zlib_deflateSetDictionary(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
 static ERL_NIF_TERM zlib_deflateReset(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
@@ -143,6 +146,11 @@ static ERL_NIF_TERM zlib_setBufSize(ErlNifEnv *env, int argc, const ERL_NIF_TERM
 static ERL_NIF_TERM zlib_enqueue_input(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
 
 static ErlNifFunc nif_funcs[] = {
+    {"close_nif", 1, zlib_close},
+    {"open_nif", 0, zlib_open},
+
+    {"set_controller_nif", 2, zlib_set_controller},
+
     /* deflate */
     {"deflateInit_nif", 6, zlib_deflateInit},
     {"deflateSetDictionary_nif", 2, zlib_deflateSetDictionary},
@@ -162,10 +170,6 @@ static ErlNifFunc nif_funcs[] = {
     /* running checksum */
     {"crc32_nif", 1, zlib_crc32},
 
-    /* open & close */
-    {"close_nif", 1, zlib_close},
-    {"open_nif", 0, zlib_open},
-
     /* The stash keeps a single term alive across calls, and is used in
      * exception_on_need_dict/1 to retain the old error behavior, and for
      * saving data flushed through deflateParams/3. */
@@ -281,9 +285,7 @@ static ERL_NIF_TERM zlib_return(ErlNifEnv *env, int code) {
     return reason;
 }
 
-static void gc_zlib(ErlNifEnv *env, void* data) {
-    zlib_data_t *d = (zlib_data_t*)data;
-
+static void zlib_internal_close(zlib_data_t *d) {
     if(d->state == ST_DEFLATE) {
         deflateEnd(&d->s);
     } else if(d->state == ST_INFLATE) {
@@ -291,8 +293,6 @@ static void gc_zlib(ErlNifEnv *env, void* data) {
     }
 
     if(d->state != ST_CLOSED) {
-        enif_ioq_destroy(d->input_queue);
-
         if(d->stash_env != NULL) {
             enif_free_env(d->stash_env);
         }
@@ -301,17 +301,36 @@ static void gc_zlib(ErlNifEnv *env, void* data) {
     }
 }
 
+static void gc_zlib(ErlNifEnv *env, void* data) {
+    zlib_data_t *d = (zlib_data_t*)data;
+
+    enif_mutex_destroy(d->controller_lock);
+    enif_ioq_destroy(d->input_queue);
+
+    zlib_internal_close(d);
+
+    (void)env;
+}
+
 static int get_zlib_data(ErlNifEnv *env, ERL_NIF_TERM opaque, zlib_data_t **d) {
     return enif_get_resource(env, opaque, rtype_zlib, (void **)d);
 }
 
 static int zlib_process_check(ErlNifEnv *env, zlib_data_t *d) {
+    int is_controlling_process;
     ErlNifPid current_process;
 
     enif_self(env, &current_process);
 
-    return enif_is_identical(enif_make_pid(env, &current_process),
+    enif_mutex_lock(d->controller_lock);
+
+    is_controlling_process = enif_is_identical(
+        enif_make_pid(env, &current_process),
         enif_make_pid(env, &d->controlling_process));
+
+    enif_mutex_unlock(d->controller_lock);
+
+    return is_controlling_process;
 }
 
 static void zlib_reset_input(zlib_data_t *d) {
@@ -516,6 +535,8 @@ static ERL_NIF_TERM zlib_open(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[
 
     d->input_queue = enif_ioq_create(ERL_NIF_IOQ_NORMAL);
 
+    d->controller_lock = enif_mutex_create("zlib_controller_lock");
+
     d->s.zalloc = zlib_alloc;
     d->s.zfree  = zlib_free;
     d->s.opaque = d;
@@ -556,7 +577,28 @@ static ERL_NIF_TERM zlib_close(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv
         return enif_raise_exception(env, am_not_initialized);
     }
 
-    gc_zlib(env, d);
+    zlib_internal_close(d);
+
+    return am_ok;
+}
+
+static ERL_NIF_TERM zlib_set_controller(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+    zlib_data_t *d;
+
+    ErlNifPid new_owner;
+
+    if(argc != 2 || !get_zlib_data(env, argv[0], &d)
+                 || !enif_get_local_pid(env, argv[1], &new_owner)) {
+        return enif_make_badarg(env);
+    } else if(!zlib_process_check(env, d)) {
+        return enif_raise_exception(env, am_not_on_controlling_process);
+    }
+
+    enif_mutex_lock(d->controller_lock);
+
+    d->controlling_process = new_owner;
+
+    enif_mutex_unlock(d->controller_lock);
 
     return am_ok;
 }
diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam
index 5048bdb846..f388bc723a 100644
Binary files a/erts/preloaded/ebin/zlib.beam and b/erts/preloaded/ebin/zlib.beam differ
diff --git a/erts/preloaded/src/zlib.erl b/erts/preloaded/src/zlib.erl
index 3170ab6351..03c9ae38a1 100644
--- a/erts/preloaded/src/zlib.erl
+++ b/erts/preloaded/src/zlib.erl
@@ -20,7 +20,8 @@
 
 -module(zlib).
 
--export([open/0,close/1,deflateInit/1,deflateInit/2,deflateInit/6,
+-export([open/0,close/1,set_controlling_process/2,
+         deflateInit/1,deflateInit/2,deflateInit/6,
          deflateSetDictionary/2,deflateReset/1,deflateParams/3,
          deflate/2,deflate/3,deflateEnd/1,
          inflateInit/1,inflateInit/2,inflateInit/3,
@@ -128,6 +129,14 @@ close(Z) ->
 close_nif(_Z) ->
     erlang:nif_error(undef).
 
+-spec set_controlling_process(Z, Pid) -> 'ok' when
+      Z :: zstream(),
+      Pid :: pid().
+set_controlling_process(Z, Pid) ->
+    set_controller_nif(Z, Pid).
+set_controller_nif(_Z, _Pid) ->
+    erlang:nif_error(undef).
+
 -spec deflateInit(Z) -> 'ok' when
       Z :: zstream().
 deflateInit(Z) ->
-- 
cgit v1.2.3