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.
@@ -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
- Data_Get_Struct(var, Environment, var_env); \
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
- Data_Get_Struct(var, Database, var_db);
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
- Data_Get_Struct(var, Transaction, var_txn)
44
+ TypedData_Get_Struct(var, Transaction, type, var_txn)
44
45
 
45
- #define CURSOR(var, var_cur) \
46
- Cursor* var_cur; \
47
- Data_Get_Struct(var, Cursor, var_cur); \
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
- const VALUE* argv;
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(Cursor* cursor);
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(Database* database);
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 environment_free(Environment *environment);
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 transaction_free(Transaction* transaction);
178
- static void transaction_mark(Transaction* transaction);
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
@@ -1,3 +1,3 @@
1
1
  module LMDB
2
- VERSION = '0.7.5'.freeze
2
+ VERSION = '0.8.0'.freeze
3
3
  end
@@ -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
@@ -27,7 +27,7 @@ end
27
27
  RSpec.configure do |c|
28
28
  c.include LMDB::SpecHelper
29
29
  c.after { FileUtils.rm_rf TEMP_ROOT }
30
- c.expect_with :rspec do |cc|
31
- cc.syntax = :should
32
- end
30
+ # c.expect_with :rspec do |cc|
31
+ # cc.syntax = :should
32
+ # end
33
33
  end