├── .travis.yml ├── LICENSE ├── README.md ├── c_src ├── async_queue.c ├── async_queue.h ├── esnappy_nif.cpp └── exc.hpp ├── rebar ├── rebar.config ├── src ├── esnappy.app.src └── esnappy.erl └── test └── text.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 18.0 4 | - 17.0 5 | - R16B03-1 6 | - R15B03 7 | - R14B04 8 | before_install: 9 | - sudo apt-get update -qq 10 | - sudo apt-get install -y libsnappy-dev 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Konstantin V. Sorokin 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. Neither the name of the copyright holder nor the names of contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | The **esnappy** library provides Erlang bindings to Google's 4 | [Snappy compression library](http://code.google.com/p/snappy/). 5 | It uses separate OS thread for compression/decompression so it won't 6 | screw up Erlang's VM scheduler while processing large data chunks. 7 | 8 | ## Erlang Version 9 | 10 | The **esnappy** library requires Erlang R14B or later. 11 | 12 | ## Building 13 | 14 | [![Build Status](https://travis-ci.org/thekvs/esnappy.svg?branch=master)](https://travis-ci.org/thekvs/esnappy) 15 | 16 | You have to have Snappy library installed on your system so that 17 | compiler can link against it. You can also specify **ESNAPPY_INCLUDE_DIR** 18 | and **ESNAPPY_LIB_DIR** enviroment variables for better control of 19 | paths used to compile and link **esnappy** library. 20 | 21 |
22 | $ ESNAPPY_INCLUDE_DIR=/usr/local/include ESNAPPY_LIB_DIR=/usr/local/lib ./rebar compile
23 | $ ./rebar eunit
24 | 
25 | 26 | ## Perfomance 27 | 28 |
29 | Erlang R14B02 (erts-5.8.3) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]
30 | 
31 | Eshell V5.8.3  (abort with ^G)
32 | 1> code:add_path("ebin").
33 | true
34 | 2> {ok, Data} = file:read_file("test/text.txt").
35 | {ok,<<32,32,32,208,155,208,181,208,178,32,208,157,208,
36 |       184,208,186,208,190,208,187,208,176,208,181,208,
37 |       178,208,...>>}
38 | 3> {ok, Ctx} = esnappy:create_ctx().
39 | {ok,<<>>}
40 | 4> {ST, {ok, SCompressed}} = timer:tc(esnappy, compress, [Ctx, Data]).
41 | {46692,
42 |  {ok,<<217,192,187,1,84,32,32,32,208,155,208,181,208,178,
43 |        32,208,157,208,184,208,186,208,190,208,187,...>>}}
44 | 5> {ZT, ZCompressed} = timer:tc(zlib, zip, [Data]).
45 | {493585,
46 |  <<172,189,203,110,37,219,145,37,56,207,175,56,17,147,146,
47 |    10,78,126,64,196,128,227,4,106,212,104,52,...>>}
48 | 6> size(Data).
49 | 3072089
50 | 7> size(SCompressed).
51 | 1548017
52 | 8> size(ZCompressed).
53 | 832898
54 | 
55 | 56 | Note the difference in execution time **46692** (Snappy) vs. **493585** (zlib). 57 | -------------------------------------------------------------------------------- /c_src/async_queue.c: -------------------------------------------------------------------------------- 1 | // vim: shiftwidth=4 expandtab 2 | #include "async_queue.h" 3 | 4 | async_queue_t* 5 | async_queue_create() 6 | { 7 | async_queue_t *aq; 8 | 9 | aq = ALLOC(sizeof(*aq)); 10 | 11 | if (!aq) { 12 | errx(1, "enif_alloc() failed"); 13 | } 14 | 15 | aq->q = ALLOC(sizeof(*(aq->q))); 16 | 17 | if (!(aq->q)) { 18 | errx(1, "enif_alloc() failed"); 19 | } 20 | 21 | TAILQ_INIT(aq->q); 22 | 23 | aq->waiting_threads = aq->len = 0; 24 | 25 | aq->mutex = enif_mutex_create("erlang_snappy_mutex"); 26 | 27 | if (!aq->mutex) { 28 | errx(1, "enif_mutex_create() failed"); 29 | } 30 | 31 | aq->cond = enif_cond_create("erlang_snappy_condvar"); 32 | 33 | if (!aq->cond) { 34 | errx(1, "enif_cond_create() failed"); 35 | } 36 | 37 | return aq; 38 | } 39 | 40 | int 41 | async_queue_length(async_queue_t *aq) 42 | { 43 | int length; 44 | 45 | MUTEX_LOCK(aq->mutex); 46 | length = aq->len - aq->waiting_threads; 47 | MUTEX_UNLOCK(aq->mutex); 48 | 49 | return length; 50 | } 51 | 52 | void * 53 | async_queue_pop(async_queue_t *aq) 54 | { 55 | struct async_queue_entry *en; 56 | void *d; 57 | 58 | MUTEX_LOCK(aq->mutex); 59 | 60 | d = NULL; 61 | aq->waiting_threads++; 62 | while (TAILQ_EMPTY(aq->q)) { 63 | enif_cond_wait(aq->cond, aq->mutex); 64 | } 65 | aq->waiting_threads--; 66 | 67 | en = TAILQ_FIRST(aq->q); 68 | TAILQ_REMOVE(aq->q, en, entries); 69 | d = en->data; 70 | aq->len--; 71 | enif_free(en); 72 | 73 | MUTEX_UNLOCK(aq->mutex); 74 | 75 | return d; 76 | } 77 | 78 | void 79 | async_queue_push(async_queue_t *aq, void *data) 80 | { 81 | struct async_queue_entry *en; 82 | 83 | MUTEX_LOCK(aq->mutex); 84 | 85 | en = ALLOC(sizeof(*en)); 86 | en->data = data; 87 | TAILQ_INSERT_TAIL(aq->q, en, entries); 88 | aq->len++; 89 | 90 | COND_SIGNAL(aq->cond); 91 | MUTEX_UNLOCK(aq->mutex); 92 | } 93 | 94 | void 95 | async_queue_destroy(async_queue_t *aq) 96 | { 97 | struct async_queue_entry *en; 98 | 99 | while (!TAILQ_EMPTY(aq->q)) { 100 | en = TAILQ_FIRST(aq->q); 101 | TAILQ_REMOVE(aq->q, en, entries); 102 | enif_free(en); 103 | } 104 | 105 | COND_DESTROY(aq->cond); 106 | MUTEX_DESTROY(aq->mutex); 107 | 108 | enif_free(aq->q); 109 | enif_free(aq); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /c_src/async_queue.h: -------------------------------------------------------------------------------- 1 | // vim: shiftwidth=4 expandtab 2 | #ifndef __ASYNC_QUEUE_H_INCLUDED__ 3 | #define __ASYNC_QUEUE_H_INCLUDED__ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | TAILQ_HEAD(queue, async_queue_entry); 22 | 23 | struct async_queue_entry { 24 | TAILQ_ENTRY(async_queue_entry) entries; 25 | void *data; 26 | }; 27 | 28 | typedef struct __async_queue { 29 | struct queue *q; 30 | ErlNifMutex *mutex; 31 | ErlNifCond *cond; 32 | int waiting_threads; 33 | int len; 34 | } async_queue_t; 35 | 36 | async_queue_t* async_queue_create(); 37 | int async_queue_length(async_queue_t *aq); 38 | void* async_queue_pop(async_queue_t *aq); 39 | void async_queue_push(async_queue_t *aq, void *data); 40 | void async_queue_destroy(async_queue_t *aq); 41 | 42 | #define ALLOC(size) enif_alloc(size) 43 | #define MUTEX_LOCK(mutex) enif_mutex_lock(mutex) 44 | #define MUTEX_UNLOCK(mutex) enif_mutex_unlock(mutex) 45 | #define MUTEX_DESTROY(mutex) enif_mutex_destroy(mutex) 46 | #define COND_SIGNAL(condvar) enif_cond_signal(condvar) 47 | #define COND_DESTROY(condvar) enif_cond_destroy(condvar) 48 | 49 | #ifdef __cplusplus 50 | } // extern "C" 51 | #endif 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /c_src/esnappy_nif.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "exc.hpp" 5 | #include "async_queue.h" 6 | #include "snappy.h" 7 | #include "snappy-sinksource.h" 8 | 9 | static const size_t ALLOC_SIZE = 2048; 10 | 11 | class Sink: public snappy::Sink { 12 | public: 13 | 14 | Sink(): used(0) 15 | { 16 | if (!enif_alloc_binary(0, &bin)) { 17 | enif_release_binary(&bin); 18 | THROW_EXC("enif_alloc_binary() failed"); 19 | } 20 | } 21 | 22 | void Append(const char *data, size_t n) 23 | { 24 | if (data != reinterpret_cast(bin.data + used)) { 25 | memcpy(bin.data + used, data, n); 26 | } 27 | 28 | used += n; 29 | } 30 | 31 | char* GetAppendBuffer(size_t len, char*) 32 | { 33 | size_t allocated = bin.size; 34 | 35 | if ((used + len) > allocated) { 36 | size_t sz = len > ALLOC_SIZE ? len + ALLOC_SIZE - (len % ALLOC_SIZE) : ALLOC_SIZE; 37 | 38 | if (!enif_realloc_binary(&bin, allocated + sz)) { 39 | enif_release_binary(&bin); 40 | THROW_EXC("enif_realloc_binary() failed"); 41 | } 42 | } 43 | 44 | return reinterpret_cast(bin.data + used); 45 | } 46 | 47 | ErlNifBinary& GetBinary() 48 | { 49 | size_t allocated = bin.size; 50 | 51 | if (allocated > used) { 52 | if (!enif_realloc_binary(&bin, used)) { 53 | enif_release_binary(&bin); 54 | THROW_EXC("enif_realloc_binary() failed"); 55 | } 56 | } 57 | 58 | return bin; 59 | } 60 | 61 | private: 62 | 63 | ErlNifBinary bin; 64 | size_t used; 65 | }; 66 | 67 | extern "C" { 68 | 69 | typedef struct { 70 | async_queue_t *queue; 71 | ErlNifThreadOpts *topts; 72 | ErlNifTid tid; 73 | } ctx_t; 74 | 75 | typedef enum { 76 | UNKNOWN, 77 | COMPRESS, 78 | DECOMPRESS, 79 | SHUTDOWN 80 | } task_type_t; 81 | 82 | typedef struct { 83 | task_type_t type; 84 | ErlNifEnv *env; 85 | ErlNifPid pid; 86 | ERL_NIF_TERM ref; 87 | ErlNifBinary data; 88 | } task_t; 89 | 90 | static ErlNifResourceType *res_type; 91 | static ERL_NIF_TERM atom_ok; 92 | static ERL_NIF_TERM atom_error; 93 | 94 | static void cleanup_task(task_t **task); 95 | static task_t* init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, 96 | ERL_NIF_TERM orig_term); 97 | static task_t* init_empty_task(task_type_t type); 98 | static ctx_t* init_ctx(); 99 | static ERL_NIF_TERM compress(task_t *task); 100 | static ERL_NIF_TERM decompress(task_t *task); 101 | static void* worker(void *arg); 102 | 103 | void resource_dtor(ErlNifEnv *env, void *obj); 104 | int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info); 105 | 106 | void 107 | cleanup_task(task_t **task) 108 | { 109 | if ((*task)->env != NULL) { 110 | enif_free_env((*task)->env); 111 | } 112 | 113 | enif_free(*task); 114 | *task = NULL; 115 | } 116 | 117 | task_t* 118 | init_empty_task(task_type_t type) 119 | { 120 | task_t *task; 121 | 122 | task = static_cast(enif_alloc(sizeof(*task))); 123 | 124 | if (task == NULL) { 125 | goto done; 126 | } 127 | 128 | memset(task, 0, sizeof(*task)); 129 | task->type = type; 130 | 131 | done: 132 | return task; 133 | } 134 | 135 | task_t* 136 | init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, 137 | ERL_NIF_TERM orig_term) 138 | { 139 | ERL_NIF_TERM term; 140 | task_t *task; 141 | 142 | task = init_empty_task(type); 143 | 144 | task->pid = pid; 145 | task->env = enif_alloc_env(); 146 | 147 | if (task->env == NULL) { 148 | cleanup_task(&task); 149 | goto done; 150 | } 151 | 152 | term = enif_make_copy(task->env, orig_term); 153 | 154 | if (!enif_inspect_iolist_as_binary(task->env, term, &task->data)) { 155 | cleanup_task(&task); 156 | goto done; 157 | } 158 | 159 | task->ref = enif_make_copy(task->env, ref); 160 | 161 | done: 162 | return task; 163 | } 164 | 165 | ERL_NIF_TERM 166 | compress(task_t *task) 167 | { 168 | ERL_NIF_TERM result; 169 | 170 | snappy::ByteArraySource source(reinterpret_cast(task->data.data), task->data.size); 171 | 172 | Sink sink; 173 | 174 | try { 175 | snappy::Compress(&source, &sink); 176 | result = enif_make_tuple3(task->env, atom_ok, task->ref, 177 | enif_make_binary(task->env, &sink.GetBinary())); 178 | } catch (std::exception &e) { 179 | result = enif_make_tuple2(task->env, atom_error, 180 | enif_make_string(task->env, "Failed to compress", 181 | ERL_NIF_LATIN1)); 182 | } 183 | 184 | return result; 185 | } 186 | 187 | ERL_NIF_TERM 188 | decompress(task_t *task) 189 | { 190 | size_t len; 191 | bool status; 192 | 193 | ERL_NIF_TERM result; 194 | ErlNifBinary bin; 195 | 196 | len = -1; 197 | status = snappy::GetUncompressedLength( 198 | reinterpret_cast(task->data.data), 199 | task->data.size, &len); 200 | 201 | if (!status) { 202 | result = enif_make_tuple2(task->env, atom_error, 203 | enif_make_string(task->env, "Data is not compressed", 204 | ERL_NIF_LATIN1)); 205 | goto done; 206 | } 207 | 208 | if (!enif_alloc_binary(len, &bin)) { 209 | result = enif_make_tuple2(task->env, atom_error, 210 | enif_make_string(task->env, "Couldn't allocate memory", 211 | ERL_NIF_LATIN1)); 212 | goto done; 213 | } 214 | 215 | status = snappy::RawUncompress( 216 | reinterpret_cast(task->data.data), 217 | task->data.size, reinterpret_cast(bin.data)); 218 | 219 | if (!status) { 220 | result = enif_make_tuple2(task->env, atom_error, 221 | enif_make_string(task->env, "Failed to decompress", 222 | ERL_NIF_LATIN1)); 223 | goto done; 224 | } 225 | 226 | result = enif_make_tuple3(task->env, atom_ok, task->ref, 227 | enif_make_binary(task->env, &bin)); 228 | 229 | done: 230 | return result; 231 | } 232 | 233 | void* 234 | worker(void *arg) 235 | { 236 | ctx_t *ctx; 237 | task_t *task; 238 | 239 | ERL_NIF_TERM result; 240 | 241 | ctx = static_cast(arg); 242 | 243 | while (true) { 244 | task = static_cast(async_queue_pop(ctx->queue)); 245 | 246 | if (task->type == COMPRESS) { 247 | result = compress(task); 248 | } else if (task->type == DECOMPRESS) { 249 | result = decompress(task); 250 | } else if (task->type == SHUTDOWN) { 251 | break; 252 | } else { 253 | errx(1, "Unexpected task type: %i", task->type); 254 | } 255 | 256 | enif_send(NULL, &task->pid, task->env, result); 257 | cleanup_task(&task); 258 | } 259 | 260 | cleanup_task(&task); 261 | return NULL; 262 | } 263 | 264 | ctx_t* 265 | init_ctx() 266 | { 267 | int status; 268 | 269 | ctx_t *ctx = static_cast(enif_alloc_resource(res_type, sizeof(*ctx))); 270 | 271 | if (ctx == NULL) { 272 | goto done; 273 | } 274 | 275 | ctx->queue = async_queue_create(); 276 | ctx->topts = enif_thread_opts_create(const_cast("snappy_thread_opts")); 277 | 278 | status = enif_thread_create(const_cast("worker"), 279 | &ctx->tid, worker, ctx, ctx->topts); 280 | 281 | if (status != 0) { 282 | enif_release_resource(ctx); 283 | ctx = NULL; 284 | goto done; 285 | } 286 | 287 | done: 288 | return ctx; 289 | } 290 | 291 | void 292 | resource_dtor(ErlNifEnv*, void *obj) 293 | { 294 | ctx_t *ctx = static_cast(obj); 295 | task_t *task = init_empty_task(SHUTDOWN); 296 | void *result = NULL; 297 | 298 | async_queue_push(ctx->queue, static_cast(task)); 299 | enif_thread_join(ctx->tid, &result); 300 | async_queue_destroy(ctx->queue); 301 | enif_thread_opts_destroy(ctx->topts); 302 | } 303 | 304 | int 305 | load(ErlNifEnv *env, void **, ERL_NIF_TERM) 306 | { 307 | const char *mod = "erlang-snappy"; 308 | const char *name = "nif_resource"; 309 | 310 | ErlNifResourceFlags flags = static_cast(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER); 311 | 312 | res_type = enif_open_resource_type(env, mod, name, resource_dtor, flags, NULL); 313 | 314 | if (res_type == NULL) { 315 | return -1; 316 | } 317 | 318 | atom_ok = enif_make_atom(env, "ok"); 319 | atom_error = enif_make_atom(env, "error"); 320 | 321 | return 0; 322 | } 323 | 324 | ERL_NIF_TERM 325 | snappy_create_ctx(ErlNifEnv *env, int, const ERL_NIF_TERM[]) 326 | { 327 | ERL_NIF_TERM rv; 328 | ctx_t *ctx; 329 | 330 | ctx = init_ctx(); 331 | 332 | if (ctx == NULL) { 333 | return enif_make_tuple2(env, atom_error, 334 | enif_make_string(env, "Failed to create context", 335 | ERL_NIF_LATIN1)); 336 | } 337 | 338 | rv = enif_make_resource(env, ctx); 339 | enif_release_resource(ctx); 340 | 341 | return enif_make_tuple2(env, atom_ok, rv); 342 | } 343 | 344 | ERL_NIF_TERM 345 | snappy_compress_impl(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 346 | { 347 | ctx_t *ctx; 348 | task_t *task; 349 | 350 | ErlNifPid pid; 351 | 352 | if (argc != 4) { 353 | return enif_make_badarg(env); 354 | } 355 | 356 | if (!enif_get_resource(env, argv[0], res_type, 357 | reinterpret_cast(&ctx))) { 358 | return enif_make_badarg(env); 359 | } 360 | 361 | if (!enif_is_ref(env, argv[1])) { 362 | return enif_make_tuple2(env, atom_error, 363 | enif_make_string(env, "Second arg. is not a reference", 364 | ERL_NIF_LATIN1)); 365 | } 366 | 367 | if (!enif_get_local_pid(env, argv[2], &pid)) { 368 | return enif_make_tuple2(env, atom_error, 369 | enif_make_string(env, "Third arg. is not a pid of local process", 370 | ERL_NIF_LATIN1)); 371 | } 372 | 373 | if (!(enif_is_binary(env, argv[3]) || 374 | enif_is_list(env, argv[3]))) { 375 | return enif_make_tuple2(env, atom_error, 376 | enif_make_string(env, "Forth arg. is not a binary or a list", 377 | ERL_NIF_LATIN1)); 378 | } 379 | 380 | task = init_task(COMPRESS, argv[1], pid, argv[3]); 381 | 382 | if (!task) { 383 | return enif_make_tuple2(env, atom_error, 384 | enif_make_string(env, "Failed to create a task", 385 | ERL_NIF_LATIN1)); 386 | } 387 | 388 | async_queue_push(ctx->queue, static_cast(task)); 389 | 390 | return atom_ok; 391 | } 392 | 393 | ERL_NIF_TERM 394 | snappy_decompress_impl(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 395 | { 396 | ctx_t *ctx; 397 | task_t *task; 398 | 399 | ErlNifPid pid; 400 | 401 | if (argc != 4) { 402 | return enif_make_badarg(env); 403 | } 404 | 405 | if (!enif_get_resource(env, argv[0], res_type, 406 | reinterpret_cast(&ctx))) { 407 | return enif_make_badarg(env); 408 | } 409 | 410 | if (!enif_is_ref(env, argv[1])) { 411 | return enif_make_tuple2(env, atom_error, 412 | enif_make_string(env, "Second arg. is not a reference", 413 | ERL_NIF_LATIN1)); 414 | } 415 | 416 | if (!enif_get_local_pid(env, argv[2], &pid)) { 417 | return enif_make_tuple2(env, atom_error, 418 | enif_make_string(env, "Third arg. is not a pid of local process", 419 | ERL_NIF_LATIN1)); 420 | } 421 | 422 | if (!(enif_is_binary(env, argv[3]) || 423 | enif_is_list(env, argv[3]))) { 424 | return enif_make_tuple2(env, atom_error, 425 | enif_make_string(env, "Forth arg. is not a binary", 426 | ERL_NIF_LATIN1)); 427 | } 428 | 429 | task = init_task(DECOMPRESS, argv[1], pid, argv[3]); 430 | 431 | if (!task) { 432 | return enif_make_tuple2(env, atom_error, 433 | enif_make_string(env, "Failed to create a task", 434 | ERL_NIF_LATIN1)); 435 | } 436 | 437 | async_queue_push(ctx->queue, static_cast(task)); 438 | 439 | return atom_ok; 440 | } 441 | 442 | static ErlNifFunc nif_funcs[] = { 443 | {"create_ctx", 0, snappy_create_ctx}, 444 | {"compress_impl", 4, snappy_compress_impl}, 445 | {"decompress_impl", 4, snappy_decompress_impl} 446 | }; 447 | 448 | ERL_NIF_INIT(esnappy, nif_funcs, &load, NULL, NULL, NULL); 449 | 450 | } // extern "C" 451 | -------------------------------------------------------------------------------- /c_src/exc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __EXC_HPP_INCLUDED__ 2 | #define __EXC_HPP_INCLUDED__ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class exc: public std::exception { 13 | public: 14 | 15 | exc(const char *file, int line, const char *fmt, ...): msg(NULL) 16 | { 17 | va_list ap; 18 | char buff[1024]; 19 | size_t len; 20 | 21 | va_start(ap, fmt); 22 | vsnprintf(buff, sizeof(buff), fmt, ap); 23 | va_end(ap); 24 | 25 | len = 0; 26 | memset(line_str, 0, sizeof(line_str)); 27 | snprintf(line_str, sizeof(line_str), "%i", line); 28 | len = strlen(buff) + 4 /* ' at ' */ + strlen(file) + 1 /* ':' */ + 29 | strlen(line_str) + 1 /* terminating zero */; 30 | msg = static_cast(calloc(1, len)); 31 | snprintf(msg, len, "%s at %s:%s", buff, sanitize_file_name(file), 32 | line_str); 33 | } 34 | 35 | virtual ~exc() throw() { 36 | free(msg); 37 | }; 38 | 39 | virtual const char *what() const throw() { 40 | return msg == NULL ? "" : msg; 41 | } 42 | 43 | private: 44 | 45 | char *msg; 46 | char line_str[sizeof("2147483647")]; 47 | 48 | const char* sanitize_file_name(const char *file) 49 | { 50 | const char *s; 51 | char *p; 52 | 53 | p = const_cast(strrchr(file, '/')); 54 | 55 | if (p == NULL) { 56 | s = file; 57 | } else { 58 | p++; 59 | s = p; 60 | } 61 | 62 | return s; 63 | } 64 | }; 65 | 66 | #define THROW_EXC(args...) (throw exc(__FILE__, __LINE__, args)) 67 | 68 | #define THROW_EXC_IF_FAILED(status, args...) do { \ 69 | if (!(status)) { \ 70 | throw exc(__FILE__, __LINE__, args); \ 71 | } \ 72 | } while(0) 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thekvs/esnappy/adbcd63dcd55bb155ce80f6782118009dd8c14cb/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {port_sources, ["c_src/*.c", "c_src/*.cpp"]}. 2 | {so_name, "esnappy_nif.so"}. 3 | {erl_opts, [debug_info]}. 4 | 5 | {port_envs, [ 6 | {"CFLAGS", "$CFLAGS -W -Wall -O3"}, 7 | {"CXXFLAGS", "$CXXFLAGS -I$ESNAPPY_INCLUDE_DIR -W -Wall -O3"}, 8 | {"LDFLAGS", "$LDFLAGS -lstdc++ -L$ESNAPPY_LIB_DIR -lsnappy"} 9 | ]}. 10 | -------------------------------------------------------------------------------- /src/esnappy.app.src: -------------------------------------------------------------------------------- 1 | {application, esnappy, 2 | [ 3 | {description, "Erlang bindings to Google's Snappy compression library"}, 4 | {vsn, "1.0"}, 5 | {modules, [ 6 | esnappy 7 | ]}, 8 | {registered, []}, 9 | {applications, [ 10 | kernel, 11 | stdlib 12 | ]}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/esnappy.erl: -------------------------------------------------------------------------------- 1 | %%% @author Konstantin Sorokin 2 | %%% 3 | %%% @copyright 2011 Konstantin V. Sorokin, All rights reserved. Open source, BSD License 4 | %%% @version 1.0 5 | %%% 6 | -module(esnappy). 7 | -version(1.0). 8 | -on_load(init/0). 9 | -export([create_ctx/0, compress/2, decompress/2]). 10 | 11 | -ifdef(TEST). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | -endif. 14 | 15 | %% @doc Initialize NIF. 16 | init() -> 17 | SoName = filename:join(case code:priv_dir(?MODULE) of 18 | {error, bad_name} -> 19 | %% this is here for testing purposes 20 | filename:join( 21 | [filename:dirname( 22 | code:which(?MODULE)),"..","priv"]); 23 | Dir -> 24 | Dir 25 | end, atom_to_list(?MODULE) ++ "_nif"), 26 | erlang:load_nif(SoName, 0). 27 | 28 | compress_impl(_Ctx, _Ref, _Self, _IoList) -> 29 | erlang:nif_error(not_loaded). 30 | 31 | decompress_impl(_Ctx, _Ref, _Self, _IoList) -> 32 | erlang:nif_error(not_loaded). 33 | 34 | create_ctx() -> 35 | erlang:nif_error(not_loaded). 36 | 37 | compress(Ctx, RawData) -> 38 | Ref = make_ref(), 39 | ok = compress_impl(Ctx, Ref, self(), RawData), 40 | receive 41 | {ok, Ref, CompressedData} -> 42 | {ok, CompressedData}; 43 | {error, Reason} -> 44 | {error, Reason}; 45 | Other -> 46 | throw(Other) 47 | end. 48 | 49 | decompress(Ctx, CompressedData) -> 50 | Ref = make_ref(), 51 | ok = decompress_impl(Ctx, Ref, self(), CompressedData), 52 | receive 53 | {ok, Ref, UncompressedData} -> 54 | {ok, UncompressedData}; 55 | {error, Reason} -> 56 | {error, Reason}; 57 | Other -> 58 | throw(Other) 59 | end. 60 | 61 | %% =================================================================== 62 | %% EUnit tests 63 | %% =================================================================== 64 | -ifdef(TEST). 65 | 66 | all_test_() -> 67 | {timeout, 120, [fun test_binary/0, 68 | fun test_iolist/0, 69 | fun test_zero_binary/0 70 | ] 71 | }. 72 | 73 | test_binary() -> 74 | {ok, Ctx} = create_ctx(), 75 | {ok, Data} = file:read_file("../test/text.txt"), 76 | CompressResult = compress(Ctx, Data), 77 | ?assertMatch({ok, _}, CompressResult), 78 | {ok, CompressedData} = CompressResult, 79 | DecompressResult = decompress(Ctx, CompressedData), 80 | ?assertMatch({ok, _}, DecompressResult), 81 | {ok, UncompressedData} = DecompressResult, 82 | ?assertEqual(true, Data =:= UncompressedData), 83 | CompressedDataSize = size(CompressedData), 84 | DataSize = size(Data), 85 | ?assertEqual(true, CompressedDataSize < DataSize), 86 | ok. 87 | 88 | test_iolist() -> 89 | {ok, Ctx} = create_ctx(), 90 | RawData = ["fshgggggggggggggggggg", <<"weqeqweqw">>], 91 | CompressResult = compress(Ctx, RawData), 92 | ?assertMatch({ok, _}, CompressResult), 93 | {ok, CompressedData} = CompressResult, 94 | DecompressResult = decompress(Ctx, CompressedData), 95 | ?assertMatch({ok, _}, DecompressResult), 96 | {ok, UncompressedData} = DecompressResult, 97 | ?assertEqual(true, list_to_binary(RawData) =:= UncompressedData), 98 | ok. 99 | 100 | test_zero_binary() -> 101 | {ok, Ctx} = create_ctx(), 102 | RawData = <<>>, 103 | CompressResult = compress(Ctx, RawData), 104 | ?assertMatch({ok, _}, CompressResult), 105 | {ok, CompressedData} = CompressResult, 106 | DecompressResult = decompress(Ctx, CompressedData), 107 | ?assertMatch({error, _}, DecompressResult), 108 | ok. 109 | 110 | -endif. 111 | --------------------------------------------------------------------------------