#include <ruby.h> #include <ruby/intern.h> #include <sys/mman.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include "mmap.h" VALUE MMAPED_FILE = Qnil; #define START_POSITION 8 #define INITIAL_SIZE (2 * sizeof(int32_t)) int open_and_extend_file(mm_ipc *i_mm, size_t len) { int fd; if ((fd = open(i_mm->t->path, i_mm->t->smode)) == -1) { rb_raise(rb_eArgError, "Can't open %s", i_mm->t->path); } if (lseek(fd, len - i_mm->t->len - 1, SEEK_END) == -1) { close(fd); rb_raise(rb_eIOError, "Can't lseek %lu", len - i_mm->t->len - 1); } if (write(fd, "\000", 1) != 1) { close(fd); rb_raise(rb_eIOError, "Can't extend %s", i_mm->t->path); } return fd; } void expand(mm_ipc *i_mm, size_t len) { if (len < i_mm->t->len) { rb_raise(rb_eArgError, "Can't reduce the size of mmap"); } if (munmap(i_mm->t->addr, i_mm->t->len)) { rb_raise(rb_eArgError, "munmap failed"); } int fd = open_and_extend_file(i_mm, len); i_mm->t->addr = mmap(0, len, i_mm->t->pmode, i_mm->t->vscope, fd, i_mm->t->offset); if (i_mm->t->addr == MAP_FAILED) { close(fd); rb_raise(rb_eArgError, "mmap failed"); } if (close(fd) == -1){ rb_raise(rb_eArgError, "Can't close %s", i_mm->t->path); } if ((i_mm->t->flag & MM_LOCK) && mlock(i_mm->t->addr, len) == -1) { rb_raise(rb_eArgError, "mlock(%d)", errno); } i_mm->t->len = len; i_mm->t->real = len; } inline uint32_t padding_length(uint32_t key_length) { return 8 - (sizeof(uint32_t) + key_length) % 8; // padding | 8 byte aligned } void save_entry(mm_ipc *i_mm, uint32_t offset, VALUE key, VALUE value){ uint32_t key_length = (uint32_t)RSTRING_LEN(key); char *pos = (char *)i_mm->t->addr + offset; memcpy(pos, &key_length, sizeof(uint32_t)); pos += sizeof(uint32_t); memmove(pos, StringValuePtr(key), key_length); pos += key_length; memset(pos, ' ', padding_length(key_length)); pos += padding_length(key_length); double val = NUM2DBL(value); memcpy(pos, &val, sizeof(double)); } inline uint32_t load_used(mm_ipc *i_mm) { uint32_t used = *((uint32_t *)i_mm->t->addr); if (used == 0){ used = START_POSITION; } return used; } inline void save_used(mm_ipc *i_mm, uint32_t used) { *((uint32_t *)i_mm->t->addr) = used; } VALUE method_load_used(VALUE self) { mm_ipc *i_mm; GET_MMAP(self, i_mm, MM_MODIFY); return UINT2NUM(load_used(i_mm)); } VALUE method_save_used(VALUE self, VALUE value) { Check_Type(value, T_FIXNUM); mm_ipc *i_mm; GET_MMAP(self, i_mm, MM_MODIFY); if (i_mm->t->len < INITIAL_SIZE) { expand(i_mm, INITIAL_SIZE); } save_used(i_mm, NUM2UINT(value)); return value; } VALUE method_add_entry(VALUE self, VALUE positions, VALUE key, VALUE value) { Check_Type(positions, T_HASH); Check_Type(key, T_STRING); VALUE position = rb_hash_lookup(positions, key); if (position != Qnil){ return position; } mm_ipc *i_mm; GET_MMAP(self, i_mm, MM_MODIFY); if (i_mm->t->flag & MM_FROZEN) { rb_error_frozen("mmap"); } if (RSTRING_LEN(key) > UINT32_MAX) { rb_raise(rb_eArgError, "string length gt %u", UINT32_MAX); } uint32_t key_length = (uint32_t)RSTRING_LEN(key); uint32_t value_offset = sizeof(uint32_t) + key_length + padding_length(key_length); uint32_t entry_length = value_offset + sizeof(double); uint32_t used = load_used(i_mm); while (i_mm->t->len < (used + entry_length)) { expand(i_mm, i_mm->t->len * 2); } save_entry(i_mm, used, key, value); save_used(i_mm, used + entry_length); return rb_hash_aset(positions, key, INT2NUM(used + value_offset)); } VALUE method_get_double(VALUE self, VALUE index) { mm_ipc *i_mm; GET_MMAP(self, i_mm, MM_MODIFY); Check_Type(index, T_FIXNUM); size_t idx = NUM2UINT(index); if ((i_mm->t->real + sizeof(double)) <= idx) { rb_raise(rb_eIndexError, "index %ld out of string", idx); } double tmp; memcpy(&tmp, (char *)i_mm->t->addr + idx, sizeof(double)); return DBL2NUM(tmp); } void Init_fast_mmaped_file() { MMAPED_FILE = rb_define_module("FastMmapedFile"); rb_define_method(MMAPED_FILE, "get_double", method_get_double, 1); rb_define_method(MMAPED_FILE, "used", method_load_used, 0); rb_define_method(MMAPED_FILE, "used=", method_save_used, 1); rb_define_method(MMAPED_FILE, "add_entry", method_add_entry, 3); }