lmdb 0.7.3 → 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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -4
  3. data/Rakefile +48 -0
  4. data/ext/lmdb_ext/extconf.rb +29 -10
  5. data/ext/lmdb_ext/lmdb_ext.c +1389 -908
  6. data/ext/lmdb_ext/lmdb_ext.h +36 -19
  7. data/lib/lmdb/database.rb +2 -1
  8. data/lib/lmdb/version.rb +1 -1
  9. data/lmdb.gemspec +4 -1
  10. data/spec/gc_torture_spec.rb +162 -0
  11. data/spec/helper.rb +3 -3
  12. data/spec/lmdb_spec.rb +152 -136
  13. data/spec/pseudo_transactions_spec.rb +237 -0
  14. data/vendor/liblmdb/VERSION +1 -0
  15. data/vendor/{libraries/liblmdb → liblmdb}/lmdb.h +18 -63
  16. data/vendor/{libraries/liblmdb → liblmdb}/mdb.c +583 -1480
  17. data/vendor/{libraries/liblmdb → liblmdb}/midl.c +0 -62
  18. data/vendor/{libraries/liblmdb → liblmdb}/midl.h +4 -16
  19. metadata +11 -34
  20. data/CONTRIBUTORS +0 -8
  21. data/behaviour.org +0 -35
  22. data/ext/lmdb_ext/prototypes.sh +0 -4
  23. data/vendor/libraries/liblmdb/.gitignore +0 -24
  24. data/vendor/libraries/liblmdb/COPYRIGHT +0 -20
  25. data/vendor/libraries/liblmdb/Doxyfile +0 -1631
  26. data/vendor/libraries/liblmdb/LICENSE +0 -47
  27. data/vendor/libraries/liblmdb/Makefile +0 -118
  28. data/vendor/libraries/liblmdb/intro.doc +0 -192
  29. data/vendor/libraries/liblmdb/mdb_copy.1 +0 -61
  30. data/vendor/libraries/liblmdb/mdb_copy.c +0 -84
  31. data/vendor/libraries/liblmdb/mdb_drop.1 +0 -40
  32. data/vendor/libraries/liblmdb/mdb_drop.c +0 -135
  33. data/vendor/libraries/liblmdb/mdb_dump.1 +0 -81
  34. data/vendor/libraries/liblmdb/mdb_dump.c +0 -319
  35. data/vendor/libraries/liblmdb/mdb_load.1 +0 -84
  36. data/vendor/libraries/liblmdb/mdb_load.c +0 -492
  37. data/vendor/libraries/liblmdb/mdb_stat.1 +0 -70
  38. data/vendor/libraries/liblmdb/mdb_stat.c +0 -264
  39. data/vendor/libraries/liblmdb/mtest.c +0 -177
  40. data/vendor/libraries/liblmdb/mtest2.c +0 -124
  41. data/vendor/libraries/liblmdb/mtest3.c +0 -133
  42. data/vendor/libraries/liblmdb/mtest4.c +0 -168
  43. data/vendor/libraries/liblmdb/mtest5.c +0 -135
  44. data/vendor/libraries/liblmdb/mtest6.c +0 -141
  45. data/vendor/libraries/liblmdb/sample-bdb.txt +0 -73
  46. data/vendor/libraries/liblmdb/sample-mdb.txt +0 -62
  47. data/vendor/libraries/liblmdb/tooltag +0 -27
@@ -0,0 +1,237 @@
1
+ require 'helper'
2
+ require 'lmdb'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ RSpec.describe 'LMDB pseudo-transactions (RO nested inside RW)' do
7
+ let(:path) { Dir.mktmpdir }
8
+ let(:env) { LMDB.new(path, mapsize: 2**20) }
9
+ let(:db) { env.database('test', create: true) }
10
+
11
+ before(:each) { db } # ensure db is opened inside a txn before tests run
12
+
13
+ after(:each) do
14
+ env.close rescue nil
15
+ FileUtils.rm_rf path
16
+ end
17
+
18
+ it 'should not have anything in the readers' do
19
+ expect(env.info[:numreaders]).to eq(0)
20
+ expect(env.reader_check).to eq(0)
21
+ expect(env.reader_list.first).to eq("(no active readers)\n")
22
+ end
23
+
24
+ # -----------------------------------------------------------------------
25
+ # The core bug: RO transaction nested inside RW must not abort the RW txn
26
+ # when the RO block raises.
27
+ # -----------------------------------------------------------------------
28
+
29
+ it 'does not abort the outer RW transaction when the inner RO block raises' do
30
+
31
+ # warn env.reader_list.inspect
32
+
33
+ expect {
34
+ env.transaction do
35
+
36
+ db['key'] = 'value'
37
+
38
+ # This inner RO transaction raises. Before the fix this silently
39
+ # aborted the outer RW transaction, causing the subsequent write
40
+ # to produce EINVAL / "Invalid argument".
41
+ begin
42
+ env.transaction(true) do
43
+ # warn env.reader_list.inspect
44
+ raise 'deliberate error inside RO block'
45
+ end
46
+ rescue RuntimeError
47
+ # swallow — the outer RW transaction should survive this
48
+ end
49
+
50
+ # This write must succeed. If the outer txn was aborted by the
51
+ # inner RO block's exception, this raises LMDB::Error::BadTxn or
52
+ # produces "Invalid argument".
53
+ db['key2'] = 'value2'
54
+ end
55
+ }.not_to raise_error
56
+
57
+ # And the writes must have actually committed
58
+ env.transaction(true) do
59
+ expect(db['key']).to eq 'value'
60
+ expect(db['key2']).to eq 'value2'
61
+ end
62
+ end
63
+
64
+ # -----------------------------------------------------------------------
65
+ # commit/abort on a pseudo-txn are no-ops: the outer RW txn survives
66
+ # -----------------------------------------------------------------------
67
+
68
+ it 'treats txn.commit inside a pseudo-transaction as a no-op' do
69
+ env.transaction do
70
+ db['before'] = 'yes'
71
+
72
+ env.transaction(true) do |pseudo|
73
+ # explicit commit on a pseudo-txn should be a no-op, not an error,
74
+ # and must not commit the outer RW transaction prematurely
75
+ pseudo.commit
76
+ end
77
+
78
+ # outer txn still alive — this must not raise
79
+ db['after'] = 'yes'
80
+ end
81
+
82
+ env.transaction(true) do
83
+ expect(db['before']).to eq 'yes'
84
+ expect(db['after']).to eq 'yes'
85
+ end
86
+ end
87
+
88
+ it 'treats txn.abort inside a pseudo-transaction as a no-op' do
89
+ env.transaction do
90
+ db['key'] = 'written'
91
+
92
+ env.transaction(true) do |pseudo|
93
+ # explicit abort on a pseudo-txn must not roll back the outer txn
94
+ pseudo.abort
95
+ end
96
+
97
+ # outer txn still alive
98
+ db['key2'] = 'also written'
99
+ end
100
+
101
+ env.transaction(true) do
102
+ expect(db['key']).to eq 'written'
103
+ expect(db['key2']).to eq 'also written'
104
+ end
105
+ end
106
+
107
+ # -----------------------------------------------------------------------
108
+ # pseudo-txn correctly reflects the read view of the enclosing RW txn
109
+ # -----------------------------------------------------------------------
110
+
111
+ it 'can read writes made in the outer RW transaction via the pseudo-transaction' do
112
+ env.transaction do
113
+ db['visible'] = 'yes'
114
+
115
+ result = env.transaction(true) do |pseudo|
116
+ # writes made in the outer txn should be visible here since
117
+ # we are using the same underlying MDB_txn
118
+ db['visible']
119
+ end
120
+
121
+ expect(result).to eq 'yes'
122
+ end
123
+ end
124
+
125
+ # -----------------------------------------------------------------------
126
+ # break from pseudo-txn exits only the inner block; outer RW continues
127
+ # -----------------------------------------------------------------------
128
+ it 'break from a pseudo-transaction exits only the inner block' do
129
+ outer_continued = false
130
+
131
+ env.transaction do
132
+ db['key'] = 'value'
133
+
134
+ env.transaction(true) do
135
+ break
136
+ end
137
+
138
+ outer_continued = true
139
+ db['key2'] = 'value2'
140
+ end
141
+
142
+ expect(outer_continued).to be true
143
+ env.transaction(true) do
144
+ expect(db['key']).to eq 'value'
145
+ expect(db['key2']).to eq 'value2'
146
+ end
147
+ end
148
+
149
+ # -----------------------------------------------------------------------
150
+ # pseudo-txn finished? predicate
151
+ # -----------------------------------------------------------------------
152
+
153
+ it 'marks the pseudo-transaction as finished after the block exits' do
154
+ pseudo_ref = nil
155
+
156
+ env.transaction do
157
+ env.transaction(true) do |pseudo|
158
+ pseudo_ref = pseudo
159
+ expect(pseudo_ref.finished?).to be false
160
+ end
161
+ end
162
+
163
+ # After the pseudo block exits, txn should be terminated
164
+ expect(pseudo_ref.finished?).to be true
165
+ end
166
+
167
+ # -----------------------------------------------------------------------
168
+ # Nested pseudo-txns (RO inside RO inside RW) also work correctly
169
+ # -----------------------------------------------------------------------
170
+
171
+ it 'handles doubly-nested pseudo-transactions without corrupting the txn chain' do
172
+ env.transaction do
173
+ db['key'] = 'value'
174
+
175
+ env.transaction(true) do
176
+ env.transaction(true) do
177
+ expect(db['key']).to eq 'value'
178
+ end
179
+ # still inside the first RO pseudo here
180
+ expect(db['key']).to eq 'value'
181
+ end
182
+
183
+ # outer RW still alive
184
+ db['key2'] = 'value2'
185
+ end
186
+
187
+ env.transaction(true) do
188
+ expect(db['key']).to eq 'value'
189
+ expect(db['key2']).to eq 'value2'
190
+ end
191
+ end
192
+
193
+ # -----------------------------------------------------------------------
194
+ # The original store-digest scenario: exception inside a method that
195
+ # opens its own RO transaction while called from inside a RW transaction.
196
+ # This is the real-world pattern that was failing.
197
+ # -----------------------------------------------------------------------
198
+
199
+ it 'survives the mark_meta_deleted pattern: RO read inside a RW write block' do
200
+ # Simulate the pattern from store-digest's v1.rb:
201
+ # mark_meta_deleted opens @lmdb.transaction (RW)
202
+ # then calls get_meta which opens @lmdb.transaction(true) (RO)
203
+ # get_meta raises
204
+ # mark_meta_deleted should survive and continue
205
+
206
+ def read_with_possible_raise(env, db, should_raise)
207
+ env.transaction(true) do
208
+ raise 'record not found' if should_raise
209
+ db['key']
210
+ end
211
+ end
212
+
213
+ expect {
214
+ env.transaction do
215
+ db['key'] = 'value'
216
+
217
+ # First inner RO call raises — simulates get_meta failing
218
+ begin
219
+ read_with_possible_raise(env, db, true)
220
+ rescue RuntimeError
221
+ # expected, swallow it
222
+ end
223
+
224
+ # Second inner RO call succeeds — simulates a subsequent read
225
+ val = read_with_possible_raise(env, db, false)
226
+ expect(val).to eq 'value'
227
+
228
+ db['key2'] = 'also committed'
229
+ end
230
+ }.not_to raise_error
231
+
232
+ env.transaction(true) do
233
+ expect(db['key']).to eq 'value'
234
+ expect(db['key2']).to eq 'also committed'
235
+ end
236
+ end
237
+ end
@@ -0,0 +1 @@
1
+ LMDB_0.9.35
@@ -53,15 +53,14 @@
53
53
  *
54
54
  * Fix: Check for stale readers periodically, using the
55
55
  * #mdb_reader_check function or the \ref mdb_stat_1 "mdb_stat" tool.
56
- * Stale writers will be cleared automatically on most systems:
56
+ * Stale writers will be cleared automatically on some systems:
57
57
  * - Windows - automatic
58
- * - BSD, systems using SysV semaphores - automatic
59
58
  * - Linux, systems using POSIX mutexes with Robust option - automatic
59
+ * - not on BSD, systems using POSIX semaphores.
60
60
  * Otherwise just make all programs using the database close it;
61
61
  * the lockfile is always reset on first open of the environment.
62
62
  *
63
- * - On BSD systems or others configured with MDB_USE_SYSV_SEM or
64
- * MDB_USE_POSIX_SEM,
63
+ * - On BSD systems or others configured with MDB_USE_POSIX_SEM,
65
64
  * startup can fail due to semaphores owned by another userid.
66
65
  *
67
66
  * Fix: Open and close the database as the user which owns the
@@ -167,8 +166,6 @@
167
166
  #define _LMDB_H_
168
167
 
169
168
  #include <sys/types.h>
170
- #include <inttypes.h>
171
- #include <limits.h>
172
169
 
173
170
  #ifdef __cplusplus
174
171
  extern "C" {
@@ -181,32 +178,6 @@ typedef int mdb_mode_t;
181
178
  typedef mode_t mdb_mode_t;
182
179
  #endif
183
180
 
184
- #ifdef _WIN32
185
- # define MDB_FMT_Z "I"
186
- #else
187
- # define MDB_FMT_Z "z" /**< printf/scanf format modifier for size_t */
188
- #endif
189
-
190
- #ifndef MDB_VL32
191
- /** Unsigned type used for mapsize, entry counts and page/transaction IDs.
192
- *
193
- * It is normally size_t, hence the name. Defining MDB_VL32 makes it
194
- * uint64_t, but do not try this unless you know what you are doing.
195
- */
196
- typedef size_t mdb_size_t;
197
- # define MDB_SIZE_MAX SIZE_MAX /**< max #mdb_size_t */
198
- /** #mdb_size_t printf formats, \b t = one of [diouxX] without quotes */
199
- # define MDB_PRIy(t) MDB_FMT_Z #t
200
- /** #mdb_size_t scanf formats, \b t = one of [dioux] without quotes */
201
- # define MDB_SCNy(t) MDB_FMT_Z #t
202
- #else
203
- typedef uint64_t mdb_size_t;
204
- # define MDB_SIZE_MAX UINT64_MAX
205
- # define MDB_PRIy(t) PRI##t##64
206
- # define MDB_SCNy(t) SCN##t##64
207
- # define mdb_env_create mdb_env_create_vl32 /**< Prevent mixing with non-VL32 builds */
208
- #endif
209
-
210
181
  /** An abstraction for a file handle.
211
182
  * On POSIX systems file handles are small integers. On Windows
212
183
  * they're opaque pointers.
@@ -229,7 +200,7 @@ typedef int mdb_filehandle_t;
229
200
  /** Library minor version */
230
201
  #define MDB_VERSION_MINOR 9
231
202
  /** Library patch version */
232
- #define MDB_VERSION_PATCH 70
203
+ #define MDB_VERSION_PATCH 35
233
204
 
234
205
  /** Combine args a,b,c into a single integer for easy version comparisons */
235
206
  #define MDB_VERINT(a,b,c) (((a) << 24) | ((b) << 16) | (c))
@@ -239,7 +210,7 @@ typedef int mdb_filehandle_t;
239
210
  MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH)
240
211
 
241
212
  /** The release date of this library version */
242
- #define MDB_VERSION_DATE "December 19, 2015"
213
+ #define MDB_VERSION_DATE "Jan 27, 2026"
243
214
 
244
215
  /** A stringifier for the version info */
245
216
  #define MDB_VERSTR(a,b,c,d) "LMDB " #a "." #b "." #c ": (" d ")"
@@ -332,8 +303,6 @@ typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *rel
332
303
  #define MDB_NORDAHEAD 0x800000
333
304
  /** don't initialize malloc'd memory before writing to datafile */
334
305
  #define MDB_NOMEMINIT 0x1000000
335
- /** use the previous snapshot rather than the latest one */
336
- #define MDB_PREVSNAPSHOT 0x2000000
337
306
  /** @} */
338
307
 
339
308
  /** @defgroup mdb_dbi_open Database Flags
@@ -343,8 +312,7 @@ typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *rel
343
312
  #define MDB_REVERSEKEY 0x02
344
313
  /** use sorted duplicates */
345
314
  #define MDB_DUPSORT 0x04
346
- /** numeric keys in native byte order, either unsigned int or #mdb_size_t.
347
- * (lmdb expects 32-bit int <= size_t <= 32/64-bit mdb_size_t.)
315
+ /** numeric keys in native byte order: either unsigned int or size_t.
348
316
  * The keys must all be of the same size. */
349
317
  #define MDB_INTEGERKEY 0x08
350
318
  /** with #MDB_DUPSORT, sorted dup items have fixed size */
@@ -480,10 +448,8 @@ typedef enum MDB_cursor_op {
480
448
  #define MDB_BAD_VALSIZE (-30781)
481
449
  /** The specified DBI was changed unexpectedly */
482
450
  #define MDB_BAD_DBI (-30780)
483
- /** Unexpected problem - txn should abort */
484
- #define MDB_PROBLEM (-30779)
485
451
  /** The last defined error code */
486
- #define MDB_LAST_ERRCODE MDB_PROBLEM
452
+ #define MDB_LAST_ERRCODE MDB_BAD_DBI
487
453
  /** @} */
488
454
 
489
455
  /** @brief Statistics for a database in the environment */
@@ -491,18 +457,18 @@ typedef struct MDB_stat {
491
457
  unsigned int ms_psize; /**< Size of a database page.
492
458
  This is currently the same for all databases. */
493
459
  unsigned int ms_depth; /**< Depth (height) of the B-tree */
494
- mdb_size_t ms_branch_pages; /**< Number of internal (non-leaf) pages */
495
- mdb_size_t ms_leaf_pages; /**< Number of leaf pages */
496
- mdb_size_t ms_overflow_pages; /**< Number of overflow pages */
497
- mdb_size_t ms_entries; /**< Number of data items */
460
+ size_t ms_branch_pages; /**< Number of internal (non-leaf) pages */
461
+ size_t ms_leaf_pages; /**< Number of leaf pages */
462
+ size_t ms_overflow_pages; /**< Number of overflow pages */
463
+ size_t ms_entries; /**< Number of data items */
498
464
  } MDB_stat;
499
465
 
500
466
  /** @brief Information about the environment */
501
467
  typedef struct MDB_envinfo {
502
468
  void *me_mapaddr; /**< Address of map, if fixed */
503
- mdb_size_t me_mapsize; /**< Size of the data memory map */
504
- mdb_size_t me_last_pgno; /**< ID of the last used page */
505
- mdb_size_t me_last_txnid; /**< ID of the last committed transaction */
469
+ size_t me_mapsize; /**< Size of the data memory map */
470
+ size_t me_last_pgno; /**< ID of the last used page */
471
+ size_t me_last_txnid; /**< ID of the last committed transaction */
506
472
  unsigned int me_maxreaders; /**< max reader slots in the environment */
507
473
  unsigned int me_numreaders; /**< max reader slots used in the environment */
508
474
  } MDB_envinfo;
@@ -648,12 +614,6 @@ int mdb_env_create(MDB_env **env);
648
614
  * caller is expected to overwrite all of the memory that was
649
615
  * reserved in that case.
650
616
  * This flag may be changed at any time using #mdb_env_set_flags().
651
- * <li>#MDB_PREVSNAPSHOT
652
- * Open the environment with the previous snapshot rather than the latest
653
- * one. This loses the latest transaction, but may help work around some
654
- * types of corruption. If opened with write access, this must be the
655
- * only process using the environment. This flag is automatically reset
656
- * after a write transaction is successfully committed.
657
617
  * </ul>
658
618
  * @param[in] mode The UNIX permissions to set on created files and semaphores.
659
619
  * This parameter is ignored on Windows.
@@ -882,7 +842,7 @@ int mdb_env_get_fd(MDB_env *env, mdb_filehandle_t *fd);
882
842
  * an active write transaction.
883
843
  * </ul>
884
844
  */
885
- int mdb_env_set_mapsize(MDB_env *env, mdb_size_t size);
845
+ int mdb_env_set_mapsize(MDB_env *env, size_t size);
886
846
 
887
847
  /** @brief Set the maximum number of threads/reader slots for the environment.
888
848
  *
@@ -995,10 +955,6 @@ int mdb_env_set_assert(MDB_env *env, MDB_assert_func *func);
995
955
  * <ul>
996
956
  * <li>#MDB_RDONLY
997
957
  * This transaction will not perform any write operations.
998
- * <li>#MDB_NOSYNC
999
- * Don't flush system buffers to disk when committing this transaction.
1000
- * <li>#MDB_NOMETASYNC
1001
- * Flush system buffers but omit metadata flush when committing this transaction.
1002
958
  * </ul>
1003
959
  * @param[out] txn Address where the new #MDB_txn handle will be stored
1004
960
  * @return A non-zero error value on failure and 0 on success. Some possible
@@ -1031,7 +987,7 @@ MDB_env *mdb_txn_env(MDB_txn *txn);
1031
987
  * @param[in] txn A transaction handle returned by #mdb_txn_begin()
1032
988
  * @return A transaction ID, valid if input is an active transaction.
1033
989
  */
1034
- mdb_size_t mdb_txn_id(MDB_txn *txn);
990
+ size_t mdb_txn_id(MDB_txn *txn);
1035
991
 
1036
992
  /** @brief Commit all the operations of a transaction into the database.
1037
993
  *
@@ -1141,8 +1097,7 @@ int mdb_txn_renew(MDB_txn *txn);
1141
1097
  * keys must be unique and may have only a single data item.
1142
1098
  * <li>#MDB_INTEGERKEY
1143
1099
  * Keys are binary integers in native byte order, either unsigned int
1144
- * or #mdb_size_t, and will be sorted as such.
1145
- * (lmdb expects 32-bit int <= size_t <= 32/64-bit mdb_size_t.)
1100
+ * or size_t, and will be sorted as such.
1146
1101
  * The keys must all be of the same size.
1147
1102
  * <li>#MDB_DUPFIXED
1148
1103
  * This flag may only be used in combination with #MDB_DUPSORT. This option
@@ -1587,7 +1542,7 @@ int mdb_cursor_del(MDB_cursor *cursor, unsigned int flags);
1587
1542
  * <li>EINVAL - cursor is not initialized, or an invalid parameter was specified.
1588
1543
  * </ul>
1589
1544
  */
1590
- int mdb_cursor_count(MDB_cursor *cursor, mdb_size_t *countp);
1545
+ int mdb_cursor_count(MDB_cursor *cursor, size_t *countp);
1591
1546
 
1592
1547
  /** @brief Compare two data items according to a particular database.
1593
1548
  *