|
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
|
|
// following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included
|
|
|
|
// in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
#include "v8.h"
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include "zlib.h"
|
|
|
|
#include "node.h"
|
|
|
|
#include "node_buffer.h"
|
|
|
|
|
|
|
|
|
|
|
|
namespace node {
|
|
|
|
using namespace v8;
|
|
|
|
|
|
|
|
|
|
|
|
static Persistent<String> callback_sym;
|
|
|
|
static Persistent<String> onerror_sym;
|
|
|
|
|
|
|
|
enum node_zlib_mode {
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
NONE,
|
|
|
|
DEFLATE,
|
|
|
|
INFLATE,
|
|
|
|
GZIP,
|
|
|
|
GUNZIP,
|
|
|
|
DEFLATERAW,
|
|
|
|
INFLATERAW,
|
|
|
|
UNZIP
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void InitZlib(v8::Handle<v8::Object> target);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deflate/Inflate
|
|
|
|
*/
|
|
|
|
class ZCtx : public ObjectWrap {
|
|
|
|
public:
|
|
|
|
|
|
|
|
ZCtx(node_zlib_mode mode)
|
|
|
|
: ObjectWrap()
|
|
|
|
, init_done_(false)
|
|
|
|
, level_(0)
|
|
|
|
, windowBits_(0)
|
|
|
|
, memLevel_(0)
|
|
|
|
, strategy_(0)
|
|
|
|
, err_(0)
|
|
|
|
, dictionary_(NULL)
|
|
|
|
, dictionary_len_(0)
|
|
|
|
, flush_(0)
|
|
|
|
, chunk_size_(0)
|
|
|
|
, write_in_progress_(false)
|
|
|
|
, mode_(mode)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
|
|
|
|
~ZCtx() {
|
|
|
|
Close();
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Close() {
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
assert(!write_in_progress_ && "write in progress");
|
|
|
|
assert(init_done_ && "close before init");
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
assert(mode_ <= UNZIP);
|
|
|
|
|
|
|
|
if (mode_ == DEFLATE || mode_ == GZIP || mode_ == DEFLATERAW) {
|
|
|
|
(void)deflateEnd(&strm_);
|
|
|
|
node_isolate->AdjustAmountOfExternalAllocatedMemory(-kDeflateContextSize);
|
|
|
|
} else if (mode_ == INFLATE || mode_ == GUNZIP || mode_ == INFLATERAW ||
|
|
|
|
mode_ == UNZIP) {
|
|
|
|
(void)inflateEnd(&strm_);
|
|
|
|
node_isolate->AdjustAmountOfExternalAllocatedMemory(-kInflateContextSize);
|
|
|
|
}
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
mode_ = NONE;
|
|
|
|
|
|
|
|
if (dictionary_ != NULL) {
|
|
|
|
delete[] dictionary_;
|
|
|
|
dictionary_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static Handle<Value> Close(const Arguments& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());
|
|
|
|
ctx->Close();
|
|
|
|
return scope.Close(Undefined(node_isolate));
|
|
|
|
}
|
|
|
|
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
|
|
|
|
// write(flush, in, in_off, in_len, out, out_off, out_len)
|
|
|
|
static Handle<Value> Write(const Arguments& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
assert(args.Length() == 7);
|
|
|
|
|
|
|
|
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());
|
|
|
|
assert(ctx->init_done_ && "write before init");
|
zlib: reduce memory consumption, release early
In zlibBuffer(), don't wait for the garbage collector to reclaim the zlib memory
but release it manually. Reduces memory consumption by a factor of 10 or more
with some workloads.
Test case:
function f() {
require('zlib').deflate('xxx', g);
}
function g() {
setTimeout(f, 5);
}
f();
Observe RSS memory usage with and without this commit. After 10,000 iterations,
RSS stabilizes at ~35 MB with this commit. Without, RSS is over 300 MB and keeps
growing.
Cause: whenever the JS object heap hits the high-water mark, the V8 GC sweeps
it clean, then tries to grow it in order to avoid more sweeps in the near
future. Rule of thumb: the bigger the JS heap, the lazier the GC can be.
A side effect of a bigger heap is that objects now live longer. This is harmless
in general but it affects zlib context objects because those are tied to large
buffers that live outside the JS heap, on the order of 16K per context object.
Ergo, don't wait for the GC to reclaim the memory - it may take a long time.
Fixes #4172.
13 years ago
|
|
|
assert(ctx->mode_ != NONE && "already finalized");
|
|
|
|
|
|
|
|
assert(!ctx->write_in_progress_ && "write already in progress");
|
|
|
|
ctx->write_in_progress_ = true;
|
|
|
|
ctx->Ref();
|
|
|
|
|
|
|
|
assert(!args[0]->IsUndefined() && "must provide flush value");
|
|
|
|
|
|
|
|
unsigned int flush = args[0]->Uint32Value();
|
|
|
|
|
|
|
|
if (flush != Z_NO_FLUSH &&
|
|
|
|
flush != Z_PARTIAL_FLUSH &&
|
|
|
|
flush != Z_SYNC_FLUSH &&
|
|
|
|
flush != Z_FULL_FLUSH &&
|
|
|
|
flush != Z_FINISH &&
|
|
|
|
flush != Z_BLOCK) {
|
|
|
|
assert(0 && "Invalid flush value");
|
|
|
|
}
|
|
|
|
|
|
|
|
Bytef *in;
|
|
|
|
Bytef *out;
|
|
|
|
size_t in_off, in_len, out_off, out_len;
|
|
|
|
|
|
|
|
if (args[1]->IsNull()) {
|
|
|
|
// just a flush
|
|
|
|
Bytef nada[1] = { 0 };
|
|
|
|
in = nada;
|
|
|
|
in_len = 0;
|
|
|
|
in_off = 0;
|
|
|
|
} else {
|
|
|
|
assert(Buffer::HasInstance(args[1]));
|
|
|
|
Local<Object> in_buf;
|
|
|
|
in_buf = args[1]->ToObject();
|
|
|
|
in_off = args[2]->Uint32Value();
|
|
|
|
in_len = args[3]->Uint32Value();
|
|
|
|
|
|
|
|
assert(in_off + in_len <= Buffer::Length(in_buf));
|
|
|
|
in = reinterpret_cast<Bytef *>(Buffer::Data(in_buf) + in_off);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(Buffer::HasInstance(args[4]));
|
|
|
|
Local<Object> out_buf = args[4]->ToObject();
|
|
|
|
out_off = args[5]->Uint32Value();
|
|
|
|
out_len = args[6]->Uint32Value();
|
|
|
|
assert(out_off + out_len <= Buffer::Length(out_buf));
|
|
|
|
out = reinterpret_cast<Bytef *>(Buffer::Data(out_buf) + out_off);
|
|
|
|
|
|
|
|
// build up the work request
|
|
|
|
uv_work_t* work_req = &(ctx->work_req_);
|
|
|
|
|
|
|
|
ctx->strm_.avail_in = in_len;
|
|
|
|
ctx->strm_.next_in = in;
|
|
|
|
ctx->strm_.avail_out = out_len;
|
|
|
|
ctx->strm_.next_out = out;
|
|
|
|
ctx->flush_ = flush;
|
|
|
|
|
|
|
|
// set this so that later on, I can easily tell how much was written.
|
|
|
|
ctx->chunk_size_ = out_len;
|
|
|
|
|
|
|
|
uv_queue_work(uv_default_loop(),
|
|
|
|
work_req,
|
|
|
|
ZCtx::Process,
|
|
|
|
ZCtx::After);
|
|
|
|
|
|
|
|
return ctx->handle_;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// thread pool!
|
|
|
|
// This function may be called multiple times on the uv_work pool
|
|
|
|
// for a single write() call, until all of the input bytes have
|
|
|
|
// been consumed.
|
|
|
|
static void Process(uv_work_t* work_req) {
|
|
|
|
ZCtx *ctx = container_of(work_req, ZCtx, work_req_);
|
|
|
|
|
|
|
|
// If the avail_out is left at 0, then it means that it ran out
|
|
|
|
// of room. If there was avail_out left over, then it means
|
|
|
|
// that all of the input was consumed.
|
|
|
|
switch (ctx->mode_) {
|
|
|
|
case DEFLATE:
|
|
|
|
case GZIP:
|
|
|
|
case DEFLATERAW:
|
|
|
|
ctx->err_ = deflate(&ctx->strm_, ctx->flush_);
|
|
|
|
break;
|
|
|
|
case UNZIP:
|
|
|
|
case INFLATE:
|
|
|
|
case GUNZIP:
|
|
|
|
case INFLATERAW:
|
|
|
|
ctx->err_ = inflate(&ctx->strm_, ctx->flush_);
|
|
|
|
|
|
|
|
// If data was encoded with dictionary
|
|
|
|
if (ctx->err_ == Z_NEED_DICT && ctx->dictionary_ != NULL) {
|
|
|
|
|
|
|
|
// Load it
|
|
|
|
ctx->err_ = inflateSetDictionary(&ctx->strm_,
|
|
|
|
ctx->dictionary_,
|
|
|
|
ctx->dictionary_len_);
|
|
|
|
if (ctx->err_ == Z_OK) {
|
|
|
|
|
|
|
|
// And try to decode again
|
|
|
|
ctx->err_ = inflate(&ctx->strm_, ctx->flush_);
|
|
|
|
} else if (ctx->err_ == Z_DATA_ERROR) {
|
|
|
|
|
|
|
|
// Both inflateSetDictionary() and inflate() return Z_DATA_ERROR.
|
|
|
|
// Make it possible for After() to tell a bad dictionary from bad
|
|
|
|
// input.
|
|
|
|
ctx->err_ = Z_NEED_DICT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0 && "wtf?");
|
|
|
|
}
|
|
|
|
|
|
|
|
// pass any errors back to the main thread to deal with.
|
|
|
|
|
|
|
|
// now After will emit the output, and
|
|
|
|
// either schedule another call to Process,
|
|
|
|
// or shift the queue and call Process.
|
|
|
|
}
|
|
|
|
|
|
|
|
// v8 land!
|
|
|
|
static void After(uv_work_t* work_req, int status) {
|
|
|
|
assert(status == 0);
|
|
|
|
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
ZCtx *ctx = container_of(work_req, ZCtx, work_req_);
|
|
|
|
|
|
|
|
// Acceptable error states depend on the type of zlib stream.
|
|
|
|
switch (ctx->err_) {
|
|
|
|
case Z_OK:
|
|
|
|
case Z_STREAM_END:
|
|
|
|
case Z_BUF_ERROR:
|
|
|
|
// normal statuses, not fatal
|
|
|
|
break;
|
|
|
|
case Z_NEED_DICT:
|
|
|
|
if (ctx->dictionary_ == NULL) {
|
|
|
|
ZCtx::Error(ctx, "Missing dictionary");
|
|
|
|
} else {
|
|
|
|
ZCtx::Error(ctx, "Bad dictionary");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
// something else.
|
|
|
|
ZCtx::Error(ctx, "Zlib error");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Local<Integer> avail_out = Integer::New(ctx->strm_.avail_out, node_isolate);
|
|
|
|
Local<Integer> avail_in = Integer::New(ctx->strm_.avail_in, node_isolate);
|
|
|
|
|
|
|
|
ctx->write_in_progress_ = false;
|
|
|
|
|
|
|
|
// call the write() cb
|
|
|
|
assert(ctx->handle_->Get(callback_sym)->IsFunction() &&
|
|
|
|
"Invalid callback");
|
|
|
|
Local<Value> args[2] = { avail_in, avail_out };
|
|
|
|
MakeCallback(ctx->handle_, callback_sym, ARRAY_SIZE(args), args);
|
|
|
|
|
|
|
|
ctx->Unref();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Error(ZCtx *ctx, const char *msg_) {
|
|
|
|
const char *msg;
|
|
|
|
if (ctx->strm_.msg != NULL) {
|
|
|
|
msg = ctx->strm_.msg;
|
|
|
|
} else {
|
|
|
|
msg = msg_;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(ctx->handle_->Get(onerror_sym)->IsFunction() &&
|
|
|
|
"Invalid error handler");
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
Local<Value> args[2] = { String::New(msg),
|
|
|
|
Local<Value>::New(node_isolate,
|
|
|
|
Number::New(ctx->err_)) };
|
|
|
|
MakeCallback(ctx->handle_, onerror_sym, ARRAY_SIZE(args), args);
|
|
|
|
|
|
|
|
// no hope of rescue.
|
|
|
|
ctx->write_in_progress_ = false;
|
|
|
|
ctx->Unref();
|
|
|
|
}
|
|
|
|
|
|
|
|
static Handle<Value> New(const Arguments& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
if (args.Length() < 1 || !args[0]->IsInt32()) {
|
|
|
|
return ThrowException(Exception::TypeError(String::New("Bad argument")));
|
|
|
|
}
|
|
|
|
node_zlib_mode mode = (node_zlib_mode) args[0]->Int32Value();
|
|
|
|
|
|
|
|
if (mode < DEFLATE || mode > UNZIP) {
|
|
|
|
return ThrowException(Exception::TypeError(String::New("Bad argument")));
|
|
|
|
}
|
|
|
|
|
|
|
|
ZCtx *ctx = new ZCtx(mode);
|
|
|
|
ctx->Wrap(args.This());
|
|
|
|
return args.This();
|
|
|
|
}
|
|
|
|
|
|
|
|
// just pull the ints out of the args and call the other Init
|
|
|
|
static Handle<Value> Init(const Arguments& args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
assert((args.Length() == 4 || args.Length() == 5) &&
|
|
|
|
"init(windowBits, level, memLevel, strategy, [dictionary])");
|
|
|
|
|
|
|
|
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());
|
|
|
|
|
|
|
|
int windowBits = args[0]->Uint32Value();
|
|
|
|
assert((windowBits >= 8 && windowBits <= 15) && "invalid windowBits");
|
|
|
|
|
|
|
|
int level = args[1]->Uint32Value();
|
|
|
|
assert((level >= -1 && level <= 9) && "invalid compression level");
|
|
|
|
|
|
|
|
int memLevel = args[2]->Uint32Value();
|
|
|
|
assert((memLevel >= 1 && memLevel <= 9) && "invalid memlevel");
|
|
|
|
|
|
|
|
int strategy = args[3]->Uint32Value();
|
|
|
|
assert((strategy == Z_FILTERED ||
|
|
|
|
strategy == Z_HUFFMAN_ONLY ||
|
|
|
|
strategy == Z_RLE ||
|
|
|
|
strategy == Z_FIXED ||
|
|
|
|
strategy == Z_DEFAULT_STRATEGY) && "invalid strategy");
|
|
|
|
|
|
|
|
char* dictionary = NULL;
|
|
|
|
size_t dictionary_len = 0;
|
|
|
|
if (args.Length() >= 5 && Buffer::HasInstance(args[4])) {
|
|
|
|
Local<Object> dictionary_ = args[4]->ToObject();
|
|
|
|
|
|
|
|
dictionary_len = Buffer::Length(dictionary_);
|
|
|
|
dictionary = new char[dictionary_len];
|
|
|
|
|
|
|
|
memcpy(dictionary, Buffer::Data(dictionary_), dictionary_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
Init(ctx, level, windowBits, memLevel, strategy,
|
|
|
|
dictionary, dictionary_len);
|
|
|
|
SetDictionary(ctx);
|
|
|
|
return Undefined(node_isolate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Handle<Value> Reset(const Arguments &args) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
ZCtx *ctx = ObjectWrap::Unwrap<ZCtx>(args.This());
|
|
|
|
|
|
|
|
Reset(ctx);
|
|
|
|
SetDictionary(ctx);
|
|
|
|
return Undefined(node_isolate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Init(ZCtx *ctx, int level, int windowBits, int memLevel,
|
|
|
|
int strategy, char* dictionary, size_t dictionary_len) {
|
|
|
|
ctx->level_ = level;
|
|
|
|
ctx->windowBits_ = windowBits;
|
|
|
|
ctx->memLevel_ = memLevel;
|
|
|
|
ctx->strategy_ = strategy;
|
|
|
|
|
|
|
|
ctx->strm_.zalloc = Z_NULL;
|
|
|
|
ctx->strm_.zfree = Z_NULL;
|
|
|
|
ctx->strm_.opaque = Z_NULL;
|
|
|
|
|
|
|
|
ctx->flush_ = Z_NO_FLUSH;
|
|
|
|
|
|
|
|
ctx->err_ = Z_OK;
|
|
|
|
|
|
|
|
if (ctx->mode_ == GZIP || ctx->mode_ == GUNZIP) {
|
|
|
|
ctx->windowBits_ += 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->mode_ == UNZIP) {
|
|
|
|
ctx->windowBits_ += 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->mode_ == DEFLATERAW || ctx->mode_ == INFLATERAW) {
|
|
|
|
ctx->windowBits_ *= -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (ctx->mode_) {
|
|
|
|
case DEFLATE:
|
|
|
|
case GZIP:
|
|
|
|
case DEFLATERAW:
|
|
|
|
ctx->err_ = deflateInit2(&ctx->strm_,
|
|
|
|
ctx->level_,
|
|
|
|
Z_DEFLATED,
|
|
|
|
ctx->windowBits_,
|
|
|
|
ctx->memLevel_,
|
|
|
|
ctx->strategy_);
|
|
|
|
node_isolate->
|
|
|
|
AdjustAmountOfExternalAllocatedMemory(kDeflateContextSize);
|
|
|
|
break;
|
|
|
|
case INFLATE:
|
|
|
|
case GUNZIP:
|
|
|
|
case INFLATERAW:
|
|
|
|
case UNZIP:
|
|
|
|
ctx->err_ = inflateInit2(&ctx->strm_, ctx->windowBits_);
|
|
|
|
node_isolate->
|
|
|
|
AdjustAmountOfExternalAllocatedMemory(kInflateContextSize);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0 && "wtf?");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->err_ != Z_OK) {
|
|
|
|
ZCtx::Error(ctx, "Init error");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ctx->dictionary_ = reinterpret_cast<Bytef *>(dictionary);
|
|
|
|
ctx->dictionary_len_ = dictionary_len;
|
|
|
|
|
|
|
|
ctx->write_in_progress_ = false;
|
|
|
|
ctx->init_done_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SetDictionary(ZCtx* ctx) {
|
|
|
|
if (ctx->dictionary_ == NULL) return;
|
|
|
|
|
|
|
|
ctx->err_ = Z_OK;
|
|
|
|
|
|
|
|
switch (ctx->mode_) {
|
|
|
|
case DEFLATE:
|
|
|
|
case DEFLATERAW:
|
|
|
|
ctx->err_ = deflateSetDictionary(&ctx->strm_,
|
|
|
|
ctx->dictionary_,
|
|
|
|
ctx->dictionary_len_);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->err_ != Z_OK) {
|
|
|
|
ZCtx::Error(ctx, "Failed to set dictionary");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Reset(ZCtx* ctx) {
|
|
|
|
ctx->err_ = Z_OK;
|
|
|
|
|
|
|
|
switch (ctx->mode_) {
|
|
|
|
case DEFLATE:
|
|
|
|
case DEFLATERAW:
|
|
|
|
ctx->err_ = deflateReset(&ctx->strm_);
|
|
|
|
break;
|
|
|
|
case INFLATE:
|
|
|
|
case INFLATERAW:
|
|
|
|
ctx->err_ = inflateReset(&ctx->strm_);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->err_ != Z_OK) {
|
|
|
|
ZCtx::Error(ctx, "Failed to reset stream");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static const int kDeflateContextSize = 16384; // approximate
|
|
|
|
static const int kInflateContextSize = 10240; // approximate
|
|
|
|
|
|
|
|
bool init_done_;
|
|
|
|
|
|
|
|
z_stream strm_;
|
|
|
|
int level_;
|
|
|
|
int windowBits_;
|
|
|
|
int memLevel_;
|
|
|
|
int strategy_;
|
|
|
|
|
|
|
|
int err_;
|
|
|
|
|
|
|
|
Bytef* dictionary_;
|
|
|
|
size_t dictionary_len_;
|
|
|
|
|
|
|
|
int flush_;
|
|
|
|
|
|
|
|
int chunk_size_;
|
|
|
|
|
|
|
|
bool write_in_progress_;
|
|
|
|
|
|
|
|
uv_work_t work_req_;
|
|
|
|
node_zlib_mode mode_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void InitZlib(Handle<Object> target) {
|
|
|
|
HandleScope scope(node_isolate);
|
|
|
|
|
|
|
|
Local<FunctionTemplate> z = FunctionTemplate::New(ZCtx::New);
|
|
|
|
|
|
|
|
z->InstanceTemplate()->SetInternalFieldCount(1);
|
|
|
|
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(z, "write", ZCtx::Write);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(z, "init", ZCtx::Init);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(z, "close", ZCtx::Close);
|
|
|
|
NODE_SET_PROTOTYPE_METHOD(z, "reset", ZCtx::Reset);
|
|
|
|
|
|
|
|
z->SetClassName(String::NewSymbol("Zlib"));
|
|
|
|
target->Set(String::NewSymbol("Zlib"), z->GetFunction());
|
|
|
|
|
|
|
|
callback_sym = NODE_PSYMBOL("callback");
|
|
|
|
onerror_sym = NODE_PSYMBOL("onerror");
|
|
|
|
|
|
|
|
// valid flush values.
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_FULL_FLUSH);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_FINISH);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_BLOCK);
|
|
|
|
|
|
|
|
// return/error codes
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_OK);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_STREAM_END);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_NEED_DICT);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_ERRNO);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_STREAM_ERROR);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_DATA_ERROR);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_MEM_ERROR);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_BUF_ERROR);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_VERSION_ERROR);
|
|
|
|
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_NO_COMPRESSION);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_BEST_SPEED);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_BEST_COMPRESSION);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_COMPRESSION);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_FILTERED);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_HUFFMAN_ONLY);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_RLE);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_FIXED);
|
|
|
|
NODE_DEFINE_CONSTANT(target, Z_DEFAULT_STRATEGY);
|
|
|
|
NODE_DEFINE_CONSTANT(target, ZLIB_VERNUM);
|
|
|
|
|
|
|
|
NODE_DEFINE_CONSTANT(target, DEFLATE);
|
|
|
|
NODE_DEFINE_CONSTANT(target, INFLATE);
|
|
|
|
NODE_DEFINE_CONSTANT(target, GZIP);
|
|
|
|
NODE_DEFINE_CONSTANT(target, GUNZIP);
|
|
|
|
NODE_DEFINE_CONSTANT(target, DEFLATERAW);
|
|
|
|
NODE_DEFINE_CONSTANT(target, INFLATERAW);
|
|
|
|
NODE_DEFINE_CONSTANT(target, UNZIP);
|
|
|
|
|
|
|
|
target->Set(String::NewSymbol("ZLIB_VERSION"), String::New(ZLIB_VERSION));
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace node
|
|
|
|
|
|
|
|
NODE_MODULE(node_zlib, node::InitZlib)
|