├── .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 | [](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 |
--------------------------------------------------------------------------------