lmdb 0.5.3 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c02c1c99b143e331bf5fdbae711a57c4845b32ca07c4c041c9c639b96cbb8388
4
- data.tar.gz: ce305105acf85f4161e654dc80ad045baeded4e8a8e39f45fed9f8da68f5c2ce
3
+ metadata.gz: 2eac165bb0261c0af6813f16df9b725b2ca2a63f4c5de98734473d9976930735
4
+ data.tar.gz: 3aa0562060e4586dd7c7a44e795adc1449f489f57634787ef5e6480abbd49a48
5
5
  SHA512:
6
- metadata.gz: 1303411d1515bf39a6406540f3a10d9e12c2bd3fea597d17a9e2b949bcf7707244381467a624edd15fada41ac65ae87721c2e42a9ec4dadb5b37e9f8db2a55b0
7
- data.tar.gz: aeaa5861dcbde41e9fec9634f6cae749dce6a1348ab9541f90247c2cff60dcb10180701348757a00ba7fc11e93431ef97cfb3064e5face3cc50c704498ca183d
6
+ metadata.gz: 22cc9f78002a05aa25fba212e4d3e3e97566ed7a9bd2e0c84996297398131b5afd57da0e1510a27086ac4b1938401d1369393f9b0515bdd044856109510c6432
7
+ data.tar.gz: eea983ccb0638bed2dbcfcd3e2ea76271072ea91d259b95e392d505c26f184c351f89ae6076025b93f47bfec3e42add3ec41055e5edb67bee5bfec2e8e341a80
data/.gitignore CHANGED
@@ -11,3 +11,5 @@ doc/
11
11
  .yardoc/
12
12
  *.gem
13
13
  dtrace/
14
+ \#*\#
15
+ .\#*
data/CONTRIBUTORS CHANGED
@@ -5,3 +5,4 @@ Julien Ammous <schmurfy@gmail.com>
5
5
  Nathaniel Pierce <nwpierce@gmail.com>
6
6
  Richard Golding <golding@chrysaetos.org>
7
7
  Joel VanderWerf <vjoel@users.sourceforge.net>
8
+ Dorian Taylor <code@doriantaylor.com>
data/Rakefile CHANGED
@@ -7,12 +7,21 @@ PRJ = File.basename(GEMSPEC, ".gemspec")
7
7
  require 'bundler/setup'
8
8
  require 'rspec/core/rake_task'
9
9
  require 'rake/extensiontask'
10
+ require 'ruby_memcheck'
11
+ require 'ruby_memcheck/rspec/rake_task'
12
+
13
+ RubyMemcheck.config(binary_name: 'lmdb_ext')
10
14
 
11
15
  RSpec::Core::RakeTask.new :spec
12
16
  Rake::ExtensionTask.new :lmdb_ext
13
17
 
18
+
14
19
  task :default => [:compile, :spec]
15
20
 
21
+ namespace :spec do
22
+ RubyMemcheck::RSpec::RakeTask.new(valgrind: :compile)
23
+ end
24
+
16
25
  def version
17
26
  @version ||= begin
18
27
  require "#{PRJ}/version"
data/behaviour.org ADDED
@@ -0,0 +1,35 @@
1
+ #+STARTUP: showall hidestars
2
+ * the situtation
3
+ - lmdb has two kinds of transaction: read-write and read-only
4
+ - there are two properties associated with transactions:
5
+ - nesting
6
+ - multiplicity
7
+ - *read-write* transactions can nest, but there can only be one stack of read-write transactions per *database*, that is, amongst /all/ threads and all processes attached to the persitent storage.
8
+ - *read-only* transactions /cannot/ nest (because it's not meaningful for a read-only transaction to nest), but there can be a read-only transaction per thread (and of course multiple threads per process).
9
+ - (i can't remember if you can have a thread with a read-write *and* read-only transaction going but we can probably assume not)
10
+ - so therefore we need a way to distinguish between read-write and read-only as well as identify the thread /across processes/ that has the one read-write transaction open.
11
+ * undocumented behaviour
12
+ ** ~mdb_txn_begin~
13
+ - if the environment is read-only and the transaction is read-write, returns ~EACCES~
14
+ - if there is a parent transaction and the current transaction's flags are ~MDB_RDONLY~ or ~MDB_WRITEMAP~ (?) or ~TXN_BLOCKED~
15
+ - if the *parent's* transaction is ~MDB_TXN_RDONLY~ (which is the same as ~MDB_RDONLY~), return ~EINVAL~
16
+ - that's saying "read-only transactions can't be nested"
17
+ - otherwise, return ~MDB_BAD_TXN~
18
+ - this is saying "read-only transactions can't be children of read-write parents"
19
+ - otherwise a few boring scenarios where the function may return ~ENOMEM~
20
+ - otherwise check ~mdb_cursor_shadow~ or ~mdb_txn_renew0~
21
+ - XXX does ~mdb_txn_begin~ block when waiting for the read-write??
22
+ * desired behaviour
23
+ ** ruby interface
24
+ - when the ruby programmer opens a read-only transaction within a read-only transaction, this should be a noop
25
+ - don't push any stack, don't allocate any resources, just do nothing
26
+ - when the ruby programmer opens a read-only transaction within a read-/write/ transaction, this should raise an exception
27
+ - in practice there's no harm except that this is more about communicating the right thing to the ruby programmer
28
+ - do we warn?
29
+ - problem: there's no way to know (via the lmdb api) if another process has a read-write transaction open
30
+ - poll?
31
+ - actually no it probably doesn't matter (the mdb api blocks anyway?)
32
+ - /actually/ actually, the cursed-ass goto pseudo-loop containing the ~CALL_WITHOUT_GVL~ deals with that
33
+ ** internal implementation
34
+ - a successfully-created read-write transaction has to set ~rw_txn_thread~ to the current thread (unless it is a sub-transaction in which case noop)
35
+ - when a read-write transaction is committed or aborted, ~rw_txn_thread~ has to be set back to null (unless the transaction has a parent)
@@ -1,6 +1,7 @@
1
1
  require 'mkmf'
2
2
 
3
3
  $CFLAGS = '-std=c99 -Wall -g'
4
+ $CFLAGS << ' -fdeclspec' if /darwin/.match? RUBY_PLATFORM
4
5
 
5
6
  # Embed lmdb if we cannot find it
6
7
  if enable_config("bundled-lmdb", false) || !(find_header('lmdb.h') && have_library('lmdb', 'mdb_env_create'))
@@ -26,6 +26,7 @@ static void check(int code) {
26
26
 
27
27
  const char* err = mdb_strerror(code);
28
28
  const char* sep = strchr(err, ':');
29
+ // increment the offset by two in case there is a colon (plus space)
29
30
  if (sep)
30
31
  err = sep + 2;
31
32
 
@@ -37,7 +38,7 @@ static void check(int code) {
37
38
  }
38
39
 
39
40
  static void transaction_free(Transaction* transaction) {
40
- if (transaction->txn) {
41
+ if (transaction->txn && !NIL_P(transaction->txn)) {
41
42
  //int id = (int)mdb_txn_id(transaction->txn);
42
43
  //rb_warn(sprintf("Memory leak: Garbage collecting active transaction %d", id));
43
44
  rb_warn("Memory leak: Garbage collecting active transaction");
@@ -49,6 +50,7 @@ static void transaction_free(Transaction* transaction) {
49
50
 
50
51
  static void transaction_mark(Transaction* transaction) {
51
52
  rb_gc_mark(transaction->parent);
53
+ rb_gc_mark(transaction->child);
52
54
  rb_gc_mark(transaction->env);
53
55
  rb_gc_mark(transaction->cursors);
54
56
  }
@@ -130,55 +132,79 @@ static VALUE transaction_env(VALUE self) {
130
132
  * @return [false,true] whether the transaction is read-only.
131
133
  */
132
134
  static VALUE transaction_is_readonly(VALUE self) {
133
- TRANSACTION(self, transaction);
134
- //MDB_txn* txn = transaction->txn;
135
- return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
135
+ TRANSACTION(self, transaction);
136
+ //MDB_txn* txn = transaction->txn;
137
+ return (transaction->flags & MDB_RDONLY) ? Qtrue : Qfalse;
136
138
  }
137
139
 
138
140
 
139
141
  static void transaction_finish(VALUE self, int commit) {
140
- TRANSACTION(self, transaction);
141
-
142
- if (!transaction->txn)
143
- rb_raise(cError, "Transaction is terminated");
144
-
145
- if (transaction->thread != rb_thread_current())
146
- rb_raise(cError, "Wrong thread");
147
-
148
- // Check nesting
149
- VALUE p = environment_active_txn(transaction->env);
150
- while (!NIL_P(p) && p != self) {
151
- TRANSACTION(p, txn);
152
- p = txn->parent;
153
- }
154
- if (p != self)
155
- rb_raise(cError, "Transaction is not active");
156
-
157
- int ret = 0;
158
- if (commit)
159
- ret = mdb_txn_commit(transaction->txn);
160
- else
161
- mdb_txn_abort(transaction->txn);
162
-
163
- long i;
164
- for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
165
- VALUE cursor = RARRAY_AREF(transaction->cursors, i);
166
- cursor_close(cursor);
167
- }
168
- rb_ary_clear(transaction->cursors);
169
-
170
- // Mark child transactions as closed
171
- p = environment_active_txn(transaction->env);
142
+ TRANSACTION(self, transaction);
143
+
144
+ if (!transaction->txn)
145
+ rb_raise(cError, "Transaction is already terminated");
146
+
147
+ if (transaction->thread != rb_thread_current())
148
+ rb_raise(cError, "The thread closing the transaction "
149
+ "is not the one that opened it");
150
+
151
+ // ensure the transaction being closed is the active one
152
+ VALUE p = environment_active_txn(transaction->env);
153
+ while (!NIL_P(p) && p != self) {
154
+ TRANSACTION(p, txn);
155
+ p = txn->parent;
156
+ }
157
+ // bail out if the transaction `self` is not the active one
158
+ if (p != self)
159
+ rb_raise(cError, "Transaction is not active");
160
+
161
+ // now eliminate the cursors
162
+ long i;
163
+ for (i=0; i<RARRAY_LEN(transaction->cursors); i++) {
164
+ VALUE cursor = RARRAY_AREF(transaction->cursors, i);
165
+ cursor_close(cursor);
166
+ }
167
+ rb_ary_clear(transaction->cursors);
168
+
169
+ // now actually finish the internal transaction
170
+ int ret = 0;
171
+ if (commit)
172
+ ret = mdb_txn_commit(transaction->txn);
173
+ else
174
+ mdb_txn_abort(transaction->txn);
175
+
176
+ // eliminate child transactions
177
+ if (transaction->child) {
178
+ p = self; // again this is a VALUE
179
+ Transaction* txn = transaction; // and this is the struct
180
+
181
+ // descend into deepest child transaction
182
+ do {
183
+ p = txn->child;
184
+ // this is TRANSACTION minus the declaration
185
+ Data_Get_Struct(txn->child, Transaction, txn);
186
+ } while (txn->child);
187
+
188
+ // now we ascend back up
172
189
  while (p != self) {
173
- TRANSACTION(p, txn);
174
- txn->txn = 0;
175
- p = txn->parent;
190
+ TRANSACTION(p, txn);
191
+ txn->txn = 0;
192
+ p = txn->parent;
176
193
  }
177
- transaction->txn = 0;
194
+ }
195
+ transaction->txn = 0;
178
196
 
179
- environment_set_active_txn(transaction->env, transaction->thread, transaction->parent);
197
+ // no more active read-write transaction; unset the registry
198
+ if (!(transaction->flags & MDB_RDONLY) && !transaction->parent) {
199
+ ENVIRONMENT(transaction->env, env);
200
+ env->rw_txn_thread = NULL;
201
+ }
180
202
 
181
- check(ret);
203
+ // now set the active transaction to the parent, if there is one
204
+ environment_set_active_txn(transaction->env, transaction->thread,
205
+ transaction->parent);
206
+
207
+ check(ret);
182
208
  }
183
209
 
184
210
  // Ruby 1.8.7 compatibility
@@ -219,65 +245,143 @@ static void stop_txn_begin(void *arg)
219
245
  txn_args->stop = 1;
220
246
  }
221
247
 
248
+ /**
249
+ * This is the code that opens transactions. Read-write transactions
250
+ * have to be called outside the GVL because they will block without
251
+ *
252
+ *
253
+ * Here is the basic problem with LMDB transactions:
254
+ *
255
+ * - There can only be one read-write transaction per LMDB
256
+ * ENVIRONMENT, not process, not thread.
257
+ *
258
+ * - Read-write transactions can nest.
259
+ *
260
+ * - There can only be one active transaction per thread.
261
+ *
262
+ * - Every thread can have an active read-ONLY transaction, but only one.
263
+ *
264
+ * - This is because read-only transactions canNOT nest (it is
265
+ * meaningless to have a nested read-only transaction)
266
+ *
267
+ * - Furthermore it is an error to open a read-only transaction under
268
+ * a read-write transaction.
269
+ *
270
+ * - Same goes for opening a read-write transaction in the same thread
271
+ * as an active read-only transaction.
272
+ *
273
+ * Nevertheless, the downstream Ruby user may not be able to
274
+ * completely control calls to env.transaction (e.g. if they are
275
+ * wrapping a transaction around a proc or lambda that happens to
276
+ * contain its own transaction), plus every call in LMDB needs to be
277
+ * implicitly wrapped in a transaction anyway.
278
+ *
279
+ * What this means is that we will, first, have to keep explicit track
280
+ * of the read-write transaction, if one exists. Second, we will have
281
+ * to simulate nesting for read-only transactions, so the behaviour in
282
+ * Ruby is the same as a read-write transaction.
283
+ *
284
+ */
285
+
222
286
  static VALUE with_transaction(VALUE venv, VALUE(*fn)(VALUE), VALUE arg, int flags) {
223
- ENVIRONMENT(venv, environment);
287
+ ENVIRONMENT(venv, environment);
224
288
 
225
- MDB_txn* txn;
226
- TxnArgs txn_args;
289
+ MDB_txn* txn;
290
+ TxnArgs txn_args;
227
291
 
228
- retry:
229
- txn = NULL;
292
+ VALUE thread = rb_thread_current();
293
+ VALUE vparent = environment_active_txn(venv);
230
294
 
231
- txn_args.env = environment->env;
232
- txn_args.parent = active_txn(venv);
233
- txn_args.flags = flags;
234
- txn_args.htxn = &txn;
235
- txn_args.result = 0;
236
- txn_args.stop = 0;
295
+ Transaction* tparent = NULL;
296
+ if (vparent && !NIL_P(vparent))
297
+ Data_Get_Struct(vparent, Transaction, tparent);
237
298
 
238
- if (flags & MDB_RDONLY) {
239
- call_txn_begin(&txn_args);
240
- }
241
- else {
242
- CALL_WITHOUT_GVL(
243
- call_txn_begin, &txn_args,
244
- stop_txn_begin, &txn_args);
245
-
246
- if (txn_args.stop || !txn) {
247
- // !txn is when rb_thread_call_without_gvl2
248
- // returns before calling txn_begin
249
- if (txn) mdb_txn_abort(txn);
250
-
251
- //rb_warn("got here lol");
252
- rb_thread_check_ints();
253
- goto retry; // in what cases do we get here?
254
- }
299
+ // rb_warn("fart lol");
300
+
301
+ // XXX note this is a cursed goto loop that could almost certainly
302
+ // be rewritten as a do-while
303
+ retry:
304
+ txn = NULL;
305
+
306
+ txn_args.env = environment->env;
307
+ txn_args.parent = active_txn(venv);
308
+ txn_args.flags = flags;
309
+ txn_args.htxn = &txn;
310
+ txn_args.result = 0;
311
+ txn_args.stop = 0;
312
+
313
+ if (flags & MDB_RDONLY) {
314
+ if (tparent && tparent->flags & MDB_RDONLY)
315
+ // this is a no-op: put the same actual transaction in a
316
+ // different wrapper struct
317
+ txn = txn_args.parent;
318
+ else
319
+ // this will return an error if the parent transaction is
320
+ // read-write, so we don't need to handle the case explicitly
321
+ call_txn_begin(&txn_args);
322
+ }
323
+ else {
324
+ if (tparent) {
325
+ // first we have to determine if we're on the same thread
326
+ // as the parent, which in turn must be the same as the
327
+ // environment's registry for which thread has the
328
+ // read-write transaction
329
+ if (thread != tparent->thread ||
330
+ thread != environment->rw_txn_thread)
331
+ rb_raise(cError,
332
+ "Attempt to nest transaction on a different thread");
255
333
  }
256
334
 
257
- check(txn_args.result);
258
-
259
- Transaction* transaction;
260
- VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark, transaction_free, transaction);
261
- transaction->parent = environment_active_txn(venv);
262
- transaction->env = venv;
263
- transaction->txn = txn;
264
- transaction->flags = flags;
265
- transaction->thread = rb_thread_current();
266
- transaction->cursors = rb_ary_new();
267
- environment_set_active_txn(venv, transaction->thread, vtxn);
268
-
269
- int exception;
270
- VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
271
-
272
- if (exception) {
273
- //rb_warn("lol got exception");
274
- if (vtxn == environment_active_txn(venv))
275
- transaction_abort(vtxn);
276
- rb_jump_tag(exception);
335
+ CALL_WITHOUT_GVL(call_txn_begin, &txn_args, stop_txn_begin, &txn_args);
336
+
337
+ if (txn_args.stop || !txn) {
338
+ // !txn is when rb_thread_call_without_gvl2
339
+ // returns before calling txn_begin
340
+ if (txn) {
341
+ mdb_txn_abort(txn);
342
+ txn_args.result = 0;
343
+ }
344
+
345
+ //rb_warn("got here lol");
346
+ rb_thread_check_ints();
347
+ goto retry; // in what cases do we get here?
277
348
  }
349
+
350
+ // set the thread
351
+ environment->rw_txn_thread = thread;
352
+ }
353
+
354
+ // this will raise unless result is zero
355
+ check(txn_args.result);
356
+
357
+ Transaction* transaction;
358
+ VALUE vtxn = Data_Make_Struct(cTransaction, Transaction, transaction_mark,
359
+ transaction_free, transaction);
360
+ transaction->parent = vparent;
361
+ transaction->env = venv;
362
+ transaction->txn = txn;
363
+ transaction->flags = flags;
364
+ transaction->thread = rb_thread_current();
365
+ transaction->cursors = rb_ary_new();
366
+
367
+ // set the parent's child to self
368
+ if (tparent) tparent->child = vtxn;
369
+
370
+ environment_set_active_txn(venv, transaction->thread, vtxn);
371
+
372
+ // now we run the function in the transaction
373
+ int exception;
374
+ VALUE ret = rb_protect(fn, NIL_P(arg) ? vtxn : arg, &exception);
375
+
376
+ if (exception) {
377
+ // rb_warn("lol got exception");
278
378
  if (vtxn == environment_active_txn(venv))
279
- transaction_commit(vtxn);
280
- return ret;
379
+ transaction_abort(vtxn);
380
+ rb_jump_tag(exception);
381
+ }
382
+ if (vtxn == environment_active_txn(venv))
383
+ transaction_commit(vtxn);
384
+ return ret;
281
385
  }
282
386
 
283
387
  static void environment_check(Environment* environment) {
@@ -674,7 +778,7 @@ static MDB_txn* active_txn(VALUE self) {
674
778
  return 0;
675
779
  TRANSACTION(vtxn, transaction);
676
780
  if (!transaction->txn)
677
- rb_raise(cError, "Transaction is terminated");
781
+ rb_raise(cError, "Transaction is already terminated");
678
782
  if (transaction->thread != rb_thread_current())
679
783
  rb_raise(cError, "Wrong thread");
680
784
  return transaction->txn;
@@ -47,19 +47,35 @@
47
47
  Data_Get_Struct(var, Cursor, var_cur); \
48
48
  cursor_check(var_cur)
49
49
 
50
+ /*
51
+ hey yo if you can convince hyc to add a function like
52
+ mdb_txn_get_flags or even mdb_txn_is_rdonly, you could probably get
53
+ rid of these wrapper structs and a lot of complexity, cause that is
54
+ literally the only reason why they're needed
55
+
56
+ actually no we would also need to know when a txn has a child and
57
+ there is also no mdb_txn_get_child or whatever
58
+ */
59
+
50
60
  typedef struct {
51
61
  VALUE env;
52
- VALUE parent;
62
+ VALUE parent; // ignored for ro threads
63
+ VALUE child; // ditto
53
64
  VALUE thread;
54
65
  VALUE cursors;
55
66
  MDB_txn* txn;
56
67
  unsigned int flags;
57
68
  } Transaction;
58
69
 
70
+ // we have an extra field `rw_txn_thread` here that acts as the
71
+ // registry for the single read-write transaction. if it's populated,
72
+ // that's the one
73
+
59
74
  typedef struct {
60
75
  MDB_env* env;
61
- VALUE thread_txn_hash;
62
- VALUE txn_thread_hash;
76
+ VALUE thread_txn_hash; /* transaction -> thread */
77
+ VALUE txn_thread_hash; /* thread -> transaction */
78
+ VALUE rw_txn_thread; /* the thread with the rw transaction */
63
79
  } Environment;
64
80
 
65
81
  typedef struct {
data/lib/lmdb/database.rb CHANGED
@@ -11,8 +11,8 @@ module LMDB
11
11
  # puts "at #{key}: #{value}"
12
12
  # end
13
13
  def each
14
- # maybe_txn true do
15
- env.transaction do
14
+ maybe_txn true do
15
+ # env.transaction do
16
16
  cursor do |c|
17
17
  while i = c.next
18
18
  yield(i)
@@ -56,8 +56,8 @@ module LMDB
56
56
  # @return [Enumerator] in lieu of a block.
57
57
  def each_key(&block)
58
58
  return enum_for :each_key unless block_given?
59
- # maybe_txn true do
60
- env.transaction do
59
+ maybe_txn true do
60
+ #env.transaction do
61
61
  cursor do |c|
62
62
  while (rec = c.next true)
63
63
  yield rec.first
@@ -81,8 +81,8 @@ module LMDB
81
81
  return
82
82
  end
83
83
 
84
- #maybe_txn true do
85
- env.transaction do
84
+ maybe_txn true do
85
+ # env.transaction do
86
86
  cursor do |c|
87
87
  method = :set
88
88
  while rec = c.send(method, key)
@@ -124,8 +124,9 @@ module LMDB
124
124
 
125
125
  ret = false
126
126
  # read-only txn was having trouble being nested inside a read-write
127
- #maybe_txn true do
128
- env.transaction do
127
+ maybe_txn true do
128
+ # env.transaction true do
129
+ # env.transaction do
129
130
  cursor { |c| ret = !!c.set(key, value) }
130
131
  end
131
132
  ret
data/lib/lmdb/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LMDB
2
- VERSION = '0.5.3'.freeze
2
+ VERSION = '0.6'.freeze
3
3
  end
data/lmdb.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'rake', "~> 13.0"
24
24
  s.add_development_dependency 'rake-compiler', '~> 1.1'
25
25
  s.add_development_dependency 'rspec', "~> 3.0"
26
+ s.add_development_dependency 'ruby_memcheck', "~> 1.0"
26
27
  end
data/spec/helper.rb CHANGED
@@ -2,6 +2,9 @@ require 'lmdb'
2
2
  require 'rspec'
3
3
  require 'fileutils'
4
4
 
5
+ # for valgrind
6
+ at_exit { GC.start }
7
+
5
8
  SPEC_ROOT = File.dirname(__FILE__)
6
9
  TEMP_ROOT = File.join(SPEC_ROOT, 'tmp')
7
10
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lmdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: '0.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Mendler
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2022-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby_memcheck
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
55
69
  description: lmdb is a Ruby binding to OpenLDAP Lightning MDB.
56
70
  email: mail@daniel-mendler.de
57
71
  executables: []
@@ -66,6 +80,7 @@ files:
66
80
  - Gemfile
67
81
  - README.md
68
82
  - Rakefile
83
+ - behaviour.org
69
84
  - ext/lmdb_ext/cursor_delete_flags.h
70
85
  - ext/lmdb_ext/cursor_put_flags.h
71
86
  - ext/lmdb_ext/dbi_flags.h
@@ -95,7 +110,7 @@ homepage: https://github.com/minad/lmdb
95
110
  licenses:
96
111
  - MIT
97
112
  metadata: {}
98
- post_install_message:
113
+ post_install_message:
99
114
  rdoc_options: []
100
115
  require_paths:
101
116
  - lib
@@ -110,8 +125,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
125
  - !ruby/object:Gem::Version
111
126
  version: '0'
112
127
  requirements: []
113
- rubygems_version: 3.1.2
114
- signing_key:
128
+ rubygems_version: 3.3.11
129
+ signing_key:
115
130
  specification_version: 4
116
131
  summary: Ruby bindings to Lightning MDB
117
132
  test_files: