lmdb 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/lmdb_ext/extconf.rb +2 -0
- data/ext/lmdb_ext/lmdb_ext.c +1379 -912
- data/ext/lmdb_ext/lmdb_ext.h +36 -19
- data/lib/lmdb/version.rb +1 -1
- data/spec/gc_torture_spec.rb +162 -0
- data/spec/helper.rb +3 -3
- data/spec/lmdb_spec.rb +144 -137
- data/spec/pseudo_transactions_spec.rb +237 -0
- metadata +6 -2
data/ext/lmdb_ext/lmdb_ext.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#ifndef _LMDB_EXT_H
|
|
2
2
|
#define _LMDB_EXT_H
|
|
3
3
|
|
|
4
|
+
#include <errno.h>
|
|
4
5
|
#include "ruby.h"
|
|
5
6
|
#include "lmdb.h"
|
|
6
7
|
|
|
@@ -29,24 +30,28 @@
|
|
|
29
30
|
# define RARRAY_AREF(ary,n) (RARRAY_PTR(ary)[n])
|
|
30
31
|
#endif
|
|
31
32
|
|
|
32
|
-
#define ENVIRONMENT(var, var_env)
|
|
33
|
-
Environment* var_env;
|
|
34
|
-
|
|
33
|
+
#define ENVIRONMENT(var, type, var_env) \
|
|
34
|
+
Environment* var_env; \
|
|
35
|
+
TypedData_Get_Struct(var, Environment, type, var_env); \
|
|
35
36
|
environment_check(var_env)
|
|
36
37
|
|
|
37
|
-
#define DATABASE(var, var_db)
|
|
38
|
-
Database* var_db;
|
|
39
|
-
|
|
38
|
+
#define DATABASE(var, type, var_db) \
|
|
39
|
+
Database* var_db; \
|
|
40
|
+
TypedData_Get_Struct(var, Database, type, var_db)
|
|
40
41
|
|
|
41
|
-
#define TRANSACTION(var, var_txn)
|
|
42
|
+
#define TRANSACTION(var, type, var_txn) \
|
|
42
43
|
Transaction* var_txn; \
|
|
43
|
-
|
|
44
|
+
TypedData_Get_Struct(var, Transaction, type, var_txn)
|
|
44
45
|
|
|
45
|
-
#define CURSOR(var, var_cur)
|
|
46
|
-
Cursor* var_cur;
|
|
47
|
-
|
|
46
|
+
#define CURSOR(var, type, var_cur) \
|
|
47
|
+
Cursor* var_cur; \
|
|
48
|
+
TypedData_Get_Struct(var, Cursor, type, var_cur); \
|
|
48
49
|
cursor_check(var_cur)
|
|
49
50
|
|
|
51
|
+
#define CURSOR_NOCHECK(var, type, var_cur) \
|
|
52
|
+
Cursor* var_cur; \
|
|
53
|
+
TypedData_Get_Struct(var, Cursor, type, var_cur)
|
|
54
|
+
|
|
50
55
|
/*
|
|
51
56
|
hey yo if you can convince hyc to add a function like
|
|
52
57
|
mdb_txn_get_flags or even mdb_txn_is_rdonly, you could probably get
|
|
@@ -92,7 +97,7 @@ typedef struct {
|
|
|
92
97
|
VALUE self;
|
|
93
98
|
const char* name;
|
|
94
99
|
int argc;
|
|
95
|
-
|
|
100
|
+
VALUE* argv;
|
|
96
101
|
} HelperArgs;
|
|
97
102
|
|
|
98
103
|
typedef struct {
|
|
@@ -118,6 +123,17 @@ static VALUE cEnvironment, cDatabase, cTransaction, cCursor, cError;
|
|
|
118
123
|
#include "errors.h"
|
|
119
124
|
#undef ERROR
|
|
120
125
|
|
|
126
|
+
#define GC_MARK(val) \
|
|
127
|
+
do { if ((val) && !NIL_P(val)) rb_gc_mark_movable(val); } while(0)
|
|
128
|
+
|
|
129
|
+
#ifdef HAVE_RB_GC_MARK_MOVABLE
|
|
130
|
+
#define GC_MARK_MOVABLE(val) \
|
|
131
|
+
do { if ((val) && !NIL_P(val)) rb_gc_mark_movable(val); } while(0)
|
|
132
|
+
|
|
133
|
+
#define GC_LOCATION(val) \
|
|
134
|
+
do { if ((val) && !NIL_P(val)) (val) = rb_gc_location(val); } while(0)
|
|
135
|
+
#endif
|
|
136
|
+
|
|
121
137
|
// BEGIN PROTOTYPES
|
|
122
138
|
void Init_lmdb_ext();
|
|
123
139
|
static MDB_txn* active_txn(VALUE self);
|
|
@@ -129,10 +145,10 @@ static VALUE cursor_close(VALUE self);
|
|
|
129
145
|
static VALUE cursor_count(VALUE self);
|
|
130
146
|
static VALUE cursor_delete(int argc, VALUE *argv, VALUE self);
|
|
131
147
|
static VALUE cursor_first(VALUE self);
|
|
132
|
-
static void cursor_free(Cursor* cursor);
|
|
133
148
|
static VALUE cursor_get(VALUE self);
|
|
134
149
|
static VALUE cursor_last(VALUE self);
|
|
135
|
-
static void cursor_mark(
|
|
150
|
+
static void cursor_mark(void* ptr);
|
|
151
|
+
static void cursor_free(void* ptr);
|
|
136
152
|
static VALUE cursor_next(int argc, VALUE* argv, VALUE self);
|
|
137
153
|
static VALUE cursor_prev(VALUE self);
|
|
138
154
|
static VALUE cursor_put(int argc, VALUE* argv, VALUE self);
|
|
@@ -143,7 +159,8 @@ static VALUE database_cursor(VALUE self);
|
|
|
143
159
|
static VALUE database_delete(int argc, VALUE *argv, VALUE self);
|
|
144
160
|
static VALUE database_drop(VALUE self);
|
|
145
161
|
static VALUE database_get(VALUE self, VALUE vkey);
|
|
146
|
-
static void database_mark(
|
|
162
|
+
static void database_mark(void* ptr);
|
|
163
|
+
static void database_free(void* ptr);
|
|
147
164
|
static VALUE database_put(int argc, VALUE *argv, VALUE self);
|
|
148
165
|
static VALUE database_stat(VALUE self);
|
|
149
166
|
static VALUE database_get_flags(VALUE self);
|
|
@@ -158,9 +175,9 @@ static VALUE environment_copy(VALUE self, VALUE path);
|
|
|
158
175
|
static VALUE environment_database(int argc, VALUE *argv, VALUE self);
|
|
159
176
|
static VALUE environment_databases(VALUE self);
|
|
160
177
|
static VALUE environment_flags(VALUE self);
|
|
161
|
-
static void
|
|
178
|
+
static void environment_mark(void* ptr);
|
|
179
|
+
static void environment_free(void* ptr);
|
|
162
180
|
static VALUE environment_info(VALUE self);
|
|
163
|
-
static void environment_mark(Environment* environment);
|
|
164
181
|
static VALUE environment_new(int argc, VALUE *argv, VALUE klass);
|
|
165
182
|
static int environment_options(VALUE key, VALUE value, EnvironmentOptions* options);
|
|
166
183
|
static VALUE environment_path(VALUE self);
|
|
@@ -174,8 +191,8 @@ static VALUE stat2hash(const MDB_stat* stat);
|
|
|
174
191
|
static VALUE transaction_abort(VALUE self);
|
|
175
192
|
static VALUE transaction_commit(VALUE self);
|
|
176
193
|
static void transaction_finish(VALUE self, int commit);
|
|
177
|
-
static void
|
|
178
|
-
static void
|
|
194
|
+
static void transaction_mark(void* ptr);
|
|
195
|
+
static void transaction_free(void* ptr);
|
|
179
196
|
static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags);
|
|
180
197
|
// END PROTOTYPES
|
|
181
198
|
|
data/lib/lmdb/version.rb
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'helper'
|
|
2
|
+
require 'lmdb'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
RSpec.describe 'LMDB GC safety' do
|
|
7
|
+
let(:env) { LMDB.new(path, mapsize: 2**24) }
|
|
8
|
+
let(:db) { env.database('test', create: true) }
|
|
9
|
+
|
|
10
|
+
before(:each) { db } # open db before each example
|
|
11
|
+
|
|
12
|
+
after(:each) do
|
|
13
|
+
env.close rescue nil
|
|
14
|
+
FileUtils.rm_rf path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# -----------------------------------------------------------------------
|
|
18
|
+
# 1. Basic GC.compact safety — no open transactions
|
|
19
|
+
# Verifies compaction callbacks update all VALUE references correctly
|
|
20
|
+
# -----------------------------------------------------------------------
|
|
21
|
+
it 'survives GC.compact with no open transactions' do
|
|
22
|
+
env.transaction { db['key'] = 'value' }
|
|
23
|
+
expect { GC.compact }.not_to raise_error
|
|
24
|
+
env.transaction(true) { expect(db['key']).to eq 'value' }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# -----------------------------------------------------------------------
|
|
28
|
+
# 2. GC.compact during an open read transaction
|
|
29
|
+
# Verifies the transaction object survives compaction
|
|
30
|
+
# -----------------------------------------------------------------------
|
|
31
|
+
it 'survives GC.compact with an open read transaction' do
|
|
32
|
+
env.transaction { db['key'] = 'value' }
|
|
33
|
+
env.transaction(true) do |txn|
|
|
34
|
+
expect { GC.compact }.not_to raise_error
|
|
35
|
+
expect(db['key']).to eq 'value'
|
|
36
|
+
expect(txn.finished?).to be false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# -----------------------------------------------------------------------
|
|
41
|
+
# 3. GC.compact between consecutive write transactions
|
|
42
|
+
# This is the Store::Digest scenario — write, compact, write again
|
|
43
|
+
# -----------------------------------------------------------------------
|
|
44
|
+
it 'allows a new write transaction after GC.compact' do
|
|
45
|
+
env.transaction { db['key1'] = 'value1' }
|
|
46
|
+
GC.compact
|
|
47
|
+
expect {
|
|
48
|
+
env.transaction { db['key2'] = 'value2' }
|
|
49
|
+
}.not_to raise_error
|
|
50
|
+
env.transaction(true) do
|
|
51
|
+
expect(db['key1']).to eq 'value1'
|
|
52
|
+
expect(db['key2']).to eq 'value2'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# -----------------------------------------------------------------------
|
|
57
|
+
# 4. GC.compact after multiple write transactions
|
|
58
|
+
# Stress version — compact after several writes
|
|
59
|
+
# -----------------------------------------------------------------------
|
|
60
|
+
it 'allows write transactions after repeated GC.compact calls' do
|
|
61
|
+
5.times do |i|
|
|
62
|
+
env.transaction { db["key#{i}"] = "value#{i}" }
|
|
63
|
+
GC.compact
|
|
64
|
+
end
|
|
65
|
+
env.transaction(true) do
|
|
66
|
+
5.times do |i|
|
|
67
|
+
expect(db["key#{i}"]).to eq "value#{i}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# -----------------------------------------------------------------------
|
|
73
|
+
# 5. GC.compact with a pseudo-transaction (RO nested in RW)
|
|
74
|
+
# Verifies pseudo-transaction compaction safety
|
|
75
|
+
# -----------------------------------------------------------------------
|
|
76
|
+
it 'survives GC.compact after pseudo-transaction use' do
|
|
77
|
+
env.transaction do
|
|
78
|
+
db['key'] = 'value'
|
|
79
|
+
env.transaction(true) do
|
|
80
|
+
expect(db['key']).to eq 'value'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
GC.compact
|
|
84
|
+
expect {
|
|
85
|
+
env.transaction { db['key2'] = 'value2' }
|
|
86
|
+
}.not_to raise_error
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# -----------------------------------------------------------------------
|
|
90
|
+
# 6. GC.compact with cursor use
|
|
91
|
+
# Verifies cursor compaction safety
|
|
92
|
+
# -----------------------------------------------------------------------
|
|
93
|
+
it 'survives GC.compact after cursor iteration' do
|
|
94
|
+
env.transaction do
|
|
95
|
+
5.times { |i| db["key#{i}"] = "value#{i}" }
|
|
96
|
+
end
|
|
97
|
+
env.transaction(true) do
|
|
98
|
+
db.cursor do |c|
|
|
99
|
+
c.first
|
|
100
|
+
GC.compact
|
|
101
|
+
expect(c.next).not_to be_nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
GC.compact
|
|
105
|
+
expect {
|
|
106
|
+
env.transaction { db['new'] = 'write' }
|
|
107
|
+
}.not_to raise_error
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# -----------------------------------------------------------------------
|
|
111
|
+
# 7. GC.start (sweep without compaction) — baseline
|
|
112
|
+
# Should always pass; establishes that non-compacting GC is safe
|
|
113
|
+
# -----------------------------------------------------------------------
|
|
114
|
+
it 'survives GC.start (sweep only) between write transactions' do
|
|
115
|
+
env.transaction { db['key1'] = 'value1' }
|
|
116
|
+
GC.start
|
|
117
|
+
expect {
|
|
118
|
+
env.transaction { db['key2'] = 'value2' }
|
|
119
|
+
}.not_to raise_error
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# -----------------------------------------------------------------------
|
|
123
|
+
# 8. Transaction object survives GC after finishing
|
|
124
|
+
# Verifies that a finished transaction's Ruby object can be
|
|
125
|
+
# collected without corrupting the environment
|
|
126
|
+
# -----------------------------------------------------------------------
|
|
127
|
+
it 'allows GC to collect finished transaction objects safely' do
|
|
128
|
+
10.times do |i|
|
|
129
|
+
env.transaction { db["key#{i}"] = "value#{i}" }
|
|
130
|
+
end
|
|
131
|
+
# force collection of all those transaction objects
|
|
132
|
+
GC.compact
|
|
133
|
+
GC.start
|
|
134
|
+
expect {
|
|
135
|
+
env.transaction { db['final'] = 'write' }
|
|
136
|
+
}.not_to raise_error
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# -----------------------------------------------------------------------
|
|
140
|
+
# 9. Many databases, many transactions, GC between each
|
|
141
|
+
# Closer to the Store::Digest workload profile
|
|
142
|
+
# -----------------------------------------------------------------------
|
|
143
|
+
it 'handles multiple named databases with GC.compact between writes' do
|
|
144
|
+
dbs = %w[alpha beta gamma delta].map do |name|
|
|
145
|
+
env.database(name, create: true)
|
|
146
|
+
end
|
|
147
|
+
GC.compact
|
|
148
|
+
|
|
149
|
+
10.times do |i|
|
|
150
|
+
env.transaction do
|
|
151
|
+
dbs.each { |d| d["key#{i}"] = "value#{i}" }
|
|
152
|
+
end
|
|
153
|
+
GC.compact
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
env.transaction(true) do
|
|
157
|
+
dbs.each do |d|
|
|
158
|
+
10.times { |i| expect(d["key#{i}"]).to eq "value#{i}" }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/spec/helper.rb
CHANGED