Sitelet https://github.com/erigontech/mdbx-go/commit/2e09c928e5840270a8bc51e9fb93d64f401443ee
Skip to content

Commit 2e09c92

Browse files
mdbx: expose Env.Copy* with MDBX_CP_OVERWRITE and add Env.Defrag (#223)
* mdbx: enable Env.Copy* with new MDBX_CP_* flags and add Env.Defrag - Uncomment Env.Copy/CopyFlag/CopyFD/CopyFDFlag and rewire them through the unified mdbx_env_copy / mdbx_env_copy2fd entry points. - Expose MDBX_CP_* flags (CopyDefaults, CopyForceDynamicSize, CopyDontFlush, CopyThrottleMVCC and the new CopyOverwrite for clobbering an existing target file). - Bind mdbx_env_defrag via a thin cgo helper (mdbxgo_env_defrag), expose DefragOptions / DefragResult and the MDBX_defrag_* stopping reasons. - Replace the long-commented Copy tests with working ones plus dedicated tests for the new CopyOverwrite flag and Env.Defrag. * mdbx: make CopyFDFlag portable to Windows On Windows mdbx_filehandle_t is HANDLE (void*), so the previous C.mdbx_filehandle_t(fd) where fd is uintptr would not compile under GOOS=windows. Route the call through a thin C helper that takes a uintptr_t and performs the platform-specific cast in C, so the Go side stays identical on every target. The CopyFD test now runs on Windows as well. * mdbx: drop redundant uint64_t cast in Env.Defrag r.spent_time_dot16 is already typed C.uint64_t, so the explicit conversion is flagged by unconvert. * mdbx: skip TestEnv_Defrag on Windows mdbx_env_defrag opens its own write transaction via txn_basal_start, and on Windows it trips ERROR_LOCK_VIOLATION against the env's own LockFileEx region when run on a handle that has just committed writes. The defrag API binding itself is fine — only the test scenario hits the libmdbx Windows locking interaction. * mdbx: drop Windows skip from TestEnv_Defrag Keep the failure visible in CI until libmdbx clarifies the expected mdbx_env_defrag calling sequence on Windows / fixes the LockFileEx region conflict that ERROR_LOCK_VIOLATIONs on a freshly written env. * ci: re-trigger after GitHub Actions skipped the previous push * ci: re-trigger workflows * save * mdbx: only compacting copies, defrag needs Exclusive, lint fixes Copy: a non-compacting copy silently produces an EMPTY database. libmdbx's copy_asis (v0.14.2) builds the destination meta-pages from a pristine model — trees.main.root = P_INVALID, first_unallocated = NUM_METAS — and never writes the source txn's roots or geometry into them, unlike copy_with_compacting. The data pages are copied byte-for-byte, so the target opens without complaint and reads as an empty db. That is what the "does not reproduce the committed data" note in the tests was describing. Reject copies without CopyCompact (ErrCopyNotCompacting) instead of exposing a backup API that loses data, and let Copy/CopyFD compact on their own; TestEnv_Copy and TestEnv_CopyFD now cover the flagless entry points that had no test. Defrag: open the environment Exclusive in the test, like libmdbx's mdbx_defrag tool does (MDBX_ENV_DEFAULTS|MDBX_EXCLUSIVE with an MDBX_ACCEDE fallback). Cutting off trailing pages needs the whole-file lock; taking it per transaction is what failed the win-2025 job with ERROR_LOCK_VIOLATION. The requirement is now documented on Env.Defrag. Lint (7 golangci-lint findings): intrange, fmtappendf, thelper, and the "shrinked" misspelling — DefragResult.PagesShrinked is now PagesShrunk, with the wrapper's C field renamed to match. * mdbx: cast fd straight from uintptr_t in mdbxgo_env_copy2fd Routing the file descriptor through a signed intptr_t on the way to mdbx_filehandle_t is implementation-defined for a Windows HANDLE with the high bit set (C11 6.3.1.3p3), and buys nothing on POSIX where the target is a plain int. Cast from the unsigned uintptr_t directly, matching the existing mdbxgo_tid_to_u64 idiom. * mdbx: lock the OS thread in tests that begin a write txn TestTxn_OpenDBI_zero and TestTxnEnvWarmup call Env.BeginTxn with write flags without runtime.LockOSThread, which the API explicitly requires: "BeginTxn does not call runtime.LockOSThread. Unless the Readonly flag is passed goroutines must call runtime.LockOSThread before calling BeginTxn". When the goroutine migrates between BeginTxn and the deferred Abort, the abort runs on a foreign thread, mdbx_txn_abort fails with MDBX_THREAD_MISMATCH, and Txn.abort discards that error. The write txn stays alive, so mdbx_env_close returns MDBX_BUSY and the datafile is never released. On POSIX the unlink still succeeds and nothing is noticed; on Windows the t.TempDir cleanup fails with "The process cannot access the file because it is being used by another process". setup's cleanup swallowed the Close error, which is what kept this invisible outside Windows. Report it instead, so a leaked write txn fails the test that leaked it, on every platform. * mdbx: drop the compacting-only copy guard, fixed upstream in v0.14.3 ErrCopyNotCompacting existed because libmdbx v0.14.2's copy_asis built the destination meta-pages from a pristine model and never wrote the source tree roots into them, so an as-is copy opened as an empty database. v0.14.3 (vendored in f8b27ce) copies geometry, trees.gc, trees.main, the canary and the txnid into the meta before writing it. Restore the plain lmdb-go semantics: Copy and CopyFD do an as-is copy, CopyFlag and CopyFDFlag pass the flags through untouched. Tests cover both modes against both targets. * mdbx: cut unreachable defrag constants and duplicate copy tests DefragNoObstacles is the zero of an OR-mask, so StoppingReasons == 0 already says it. DefragDiscontinued and DefragAborted are only ever raised by the progress callback, which Env.Defrag always passes as NULL, so neither can appear in a result today. TestEnv_CopyFlag_AsIs and TestEnv_CopyFDFlag_AsIs re-ran the path TestEnv_Copy and TestEnv_CopyFD already cover: Copy is defined as CopyFlag(path, CopyDefaults) and CopyDefaults is zero. TestEnv_Defrag's nil-result check tested a value Defrag builds unconditionally, and its PagesWhole assertion is implied by the PagesMoved one below it. * mdbx: test DefragOptions.TimeLimit Two things can go wrong with TimeLimit and neither shows up in TestEnv_Defrag: the duration can fail to reach libmdbx at all, since it travels as a count of 1/65536-second units rather than as a Duration, and the bound can reach it but never stop anything. Cover both. TimeLimit below TimeAtLeast is rejected by libmdbx, which it can only do after the conversion, so that proves both durations arrive. Then a millisecond limit against a database whose unbounded defrag takes tens of milliseconds must come back with DefragTimeLimit in StoppingReasons. The limit stops defrag from scheduling further work rather than aborting the batch in flight, so SpentTime can overshoot it and is not asserted. Measured 23 pages moved over 2 cycles under the limit against 5080 over 4 without it, and the stopping reason is stable over 8 runs. * mdbx: saturate NewDuration16dot16 instead of wrapping it Duration16dot16 is unsigned and time.Duration is not, so a negative duration wrapped straight through the conversion. Every libmdbx entry point that takes one of these narrows it to 32 bits, and every one reads 0 as "no bound", which turned two ordinary mistakes into silent opposites of what the caller asked for: TimeLimit: deadline.Sub(time.Now()) // deadline already passed wrapped to 18446744073709486077, whose low 32 bits are a bound of roughly 18h12m -- an unbounded defrag where the caller wanted none. And TimeLimit: 24 * time.Hour is 5662603224 ticks, past UINT32_MAX, so defrag_init's (uint32_t) cast left 5h48m. A duration landing on a multiple of 2^32 ticks left the detent equal to the start timestamp, stopping defrag after one cycle. Sub-15.26us durations rounded to 0, i.e. no bound at all. Clamp in the conversion rather than at the call sites: Env.Defrag is the new caller, but Env.SetSyncPeriod and Txn.EnvWarmup pass the same value through C.uint and had the same two bugs. Only a zero duration may now produce 0; anything negative or below the unit becomes the smallest real bound, and anything past the 32-bit ceiling saturates there. * mdbx: handle the no-reader LaggardReader, and cut duplicated test fixtures mdbx_env_defrag returns MDBX_LAGGARD_READER with no reader anywhere near it: when defrag stalls on the same page four times over it does rc = txn->dbs[FREE_DBI].items ? MDBX_LAGGARD_READER : MDBX_RESULT_TRUE; so an empty GC gives "stopped early" and a non-empty one gives an errno, for the same stall. That code escapes the MDBX_RESULT_TRUE normalization at the end of mdbx_env_defrag, so it reaches Go as a real error. Both defrag tests open Exclusive, which rules out an actual reader, yet both called t.Fatalf on any error: whether they pass depends on GC layout, page size and filesystem. Export the errno as LaggardReader, say what it really means on Env.Defrag, and let the tests tolerate it. DefragResult.PagesShrunk repeated upstream's promise that it can go negative. It cannot: libmdbx computes it as before_defrag - last_allocated, both pgno_t, so the unsigned 32-bit result zero-extends and a "negative" shrink arrives as a value near 1<<32. Say so, since `if res.PagesShrunk < 0` would never fire. TestEnv_CopyFlag_Overwrite only asserted that the second copy failed, so any unrelated breakage of CopyFlag passed as "refuses to clobber". Assert os.ErrExist, which is what libmdbx's O_EXCL open produces and which maps correctly on Windows too. The three fixtures the new tests had inlined twice each -- write one record, reopen and compare, write-then-delete for defrag -- become seedCopyItem, verifyCopyItem and seedDefrag. * mdbx: recover the sign of pages_shrinked instead of documenting its loss The previous commit papered over this by telling callers that PagesShrunk can never go negative. It can, and the information was there: libmdbx computes it as an unsigned 32-bit subtraction of two pgno_t and stores the wrapped result in a wider intptr_t, so a backwards shrink arrives zero-extended near 1<<32. MAX_PAGENO is 0x7FFFffff, so the true difference always fits in int32 and reinterpreting the low half recovers it unambiguously. Do that in the shim and restore mdbx.h's own description of the field. Also drop the gratuitous rename. The shim mirrors all fifteen members of MDBX_defrag_result_t under upstream's names; pages_shrinked was the only one respelled, which costs the 1:1 grep trail against libmdbx for nothing. The exported Go field stays PagesShrunk, since that is a Go API. No test: defrag growing the file is what produces a negative value and I could not provoke it, with or without reclaimable pages, at any size tried. The positive path stays covered by TestEnv_Defrag. * mdbx: drop the comment explaining that a field matches upstream * mdbx: put the shim field back to pages_shrunk 287ac51 renamed it to upstream's pages_shrinked to keep the C side greppable against libmdbx. That breaks the build: misspell is enabled in .golangci.yml, cgo puts the member name into env.go, and the linter rejects "shrinked" there. The original spelling was not gratuitous. Keeps the sign recovery from 287ac51, which was the point of that commit. The C statement still names the upstream field on its right-hand side, so the grep trail survives where it actually runs. * mdbx: cut the commentary back, and document the backlash clamp Fold NewDuration16dot16's zero case into the switch it sits above, and drop the repeated "0 = no bound" from every DefragOptions field now that the struct comment says it once. The rest is comment volume. Most of these explained upstream behaviour at a length that dwarfed the code: 10 lines on a 10-line duration conversion, 18 on Defrag, 6 on one cast. Kept the facts a reader cannot get from the source in front of them -- why the fd cast avoids intptr_t, why the low half of pages_shrinked recovers its sign, that LaggardReader fires with no reader, that seedDefrag needs two transactions -- and deleted the retellings. One addition, not a cut: AcceptableBacklash now says libmdbx clamps it to about 1018 pages at a 4 KiB page size. Measured on a 41 GiB chaindata, values of 10, 20, 30, 40 and 41 GiB all behaved identically to autopilot, and nothing in mdbx.h says why. net -64 lines. --------- Co-authored-by: Alexey Sharov <AskAlexSharov@gmail.com>
1 parent 52e7938 commit 2e09c92

8 files changed

Lines changed: 474 additions & 141 deletions

File tree

‎mdbx/duration.go‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package mdbx
22

3-
import "time"
3+
import (
4+
"math"
5+
"time"
6+
)
47

58
/*
69
#include <stdint.h>
@@ -9,12 +12,29 @@ import "C"
912

1013
type Duration16dot16 uint64
1114

15+
// maxDuration16dot16 (~18h12m) is the largest value libmdbx acts on: every
16+
// consumer narrows it to 32 bits.
17+
const maxDuration16dot16 = Duration16dot16(math.MaxUint32)
18+
1219
func (d Duration16dot16) ToDuration() time.Duration {
1320
return time.Duration(d) * (time.Second / 65536)
1421
}
1522

23+
// NewDuration16dot16 converts duration to libmdbx's 1/65536-second units,
24+
// saturating rather than wrapping. libmdbx reads 0 as "no bound", so only a
25+
// zero duration may produce it: negative and sub-unit durations become the
26+
// smallest bound instead.
1627
func NewDuration16dot16(duration time.Duration) Duration16dot16 {
17-
return Duration16dot16(duration / (time.Second / 65536))
28+
ticks := duration / (time.Second / 65536)
29+
switch {
30+
case duration == 0:
31+
return 0
32+
case duration < 0 || ticks == 0:
33+
return 1
34+
case Duration16dot16(ticks) > maxDuration16dot16:
35+
return maxDuration16dot16
36+
}
37+
return Duration16dot16(ticks)
1838
}
1939

2040
func toDuration(seconds16dot16 C.uint32_t) time.Duration {

‎mdbx/duration_test.go‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mdbx
22

33
import (
4+
"math"
45
"testing"
56
"time"
67
)
@@ -36,3 +37,30 @@ func TestDurationWith16dot16(t *testing.T) {
3637
assertEqual16dot16(t, NewDuration16dot16(expected.ToDuration()), expected)
3738
}
3839
}
40+
41+
// Conversions that used to wrap: libmdbx narrows to 32 bits and reads 0 as
42+
// "no bound", so neither may happen by accident.
43+
func TestNewDuration16dot16_Saturates(t *testing.T) {
44+
const unit = time.Second / 65536 // 15.258us
45+
46+
for _, tc := range []struct {
47+
name string
48+
in time.Duration
49+
want Duration16dot16
50+
}{
51+
{"zero stays no-bound", 0, 0},
52+
{"one unit", unit, 1},
53+
{"below the unit rounds up, never to no-bound", unit - 1, 1},
54+
{"negative becomes the smallest bound", -time.Second, 1},
55+
{"negative below the unit too", -1, 1},
56+
{"at the ceiling", maxDuration16dot16.ToDuration(), maxDuration16dot16},
57+
{"past the ceiling saturates", 24 * time.Hour, maxDuration16dot16},
58+
{"far past the ceiling saturates", math.MaxInt64, maxDuration16dot16},
59+
} {
60+
t.Run(tc.name, func(t *testing.T) {
61+
if got := NewDuration16dot16(tc.in); got != tc.want {
62+
t.Errorf("NewDuration16dot16(%v) = %d, want %d", tc.in, got, tc.want)
63+
}
64+
})
65+
}
66+
}

‎mdbx/env.go‎

Lines changed: 130 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,33 @@ const (
5757
MaxDbi = C.MDBX_MAX_DBI
5858
)
5959

60-
// These flags are exclusively used in the Env.CopyFlags and Env.CopyFDFlags
60+
// These flags are exclusively used in the Env.CopyFlag and Env.CopyFDFlag
6161
// methods.
6262
const (
63-
// Flags for Env.CopyFlags
63+
// Flags for Env.CopyFlag / Env.CopyFDFlag
6464
//
65-
// See mdbx_env_copy2
65+
// See mdbx_env_copy
66+
67+
CopyDefaults = C.MDBX_CP_DEFAULTS // Perform copy as-is, without compaction
68+
CopyCompact = C.MDBX_CP_COMPACT // Perform compaction while copying: omit free pages and renumber
69+
CopyForceDynamicSize = C.MDBX_CP_FORCE_DYNAMIC_SIZE // Force resizable copy (dynamic size instead of fixed)
70+
CopyDontFlush = C.MDBX_CP_DONT_FLUSH // Don't explicitly flush the written data to output media
71+
CopyThrottleMVCC = C.MDBX_CP_THROTTLE_MVCC // Use read transaction parking during copying MVCC-snapshot
72+
CopyOverwrite = C.MDBX_CP_OVERWRITE // Silently overwrite the target file if it exists
73+
)
6674

67-
CopyCompact = C.MDBX_CP_COMPACT // Perform compaction while copying
75+
// DefragResult.StoppingReasons is an OR'ed mask of these; zero means no
76+
// obstacles. MDBX_defrag_discontinued and MDBX_defrag_aborted are omitted,
77+
// being reachable only through the progress callback Env.Defrag passes as NULL.
78+
//
79+
// See MDBX_defrag_stopping_reasons_t.
80+
const (
81+
DefragStepSize = C.MDBX_defrag_step_size // Step transaction size limit reached
82+
DefragLargeChunk = C.MDBX_defrag_large_chunk // Preliminary movement is necessary
83+
DefragLaggardReader = C.MDBX_defrag_laggard_reader // A reader is preventing further defragmentation
84+
DefragEnoughThreshold = C.MDBX_defrag_enough_threshold // User-set goal achieved
85+
DefragTimeLimit = C.MDBX_defrag_time_limit // Specified time limit reached
86+
DefragError = C.MDBX_defrag_error // An error occurred during defragmentation
6887
)
6988

7089
const (
@@ -236,41 +255,40 @@ func (env *Env) Close() error {
236255
return operrno("mdbx_env_close", ret)
237256
}
238257

239-
// CopyFD copies env to the file descriptor fd.
258+
// CopyFD copies env as-is to the file descriptor fd.
240259
//
241-
// See mdbx_env_copyfd.
242-
// func (env *Env) CopyFD(fd uintptr) error {
243-
// ret := C.mdbx_env_copyfd(env._env, C.mdbx_filehandle_t(fd))
244-
// return operrno("mdbx_env_copyfd", ret)
245-
//}
260+
// See mdbx_env_copy2fd.
261+
func (env *Env) CopyFD(fd uintptr) error {
262+
return env.CopyFDFlag(fd, CopyDefaults)
263+
}
246264

247-
// CopyFDFlag copies env to the file descriptor fd, with options.
265+
// CopyFDFlag copies env to the file descriptor fd, with options. On Windows
266+
// fd must be a native HANDLE value (as returned by os.File.Fd); on POSIX it
267+
// is a regular int file descriptor.
248268
//
249-
// See mdbx_env_copyfd2.
250-
// func (env *Env) CopyFDFlag(fd uintptr, flags uint) error {
251-
// ret := C.mdbx_env_copyfd2(env._env, C.mdbx_filehandle_t(fd), C.uint(flags))
252-
// return operrno("mdbx_env_copyfd2", ret)
253-
//}
269+
// See mdbx_env_copy2fd.
270+
func (env *Env) CopyFDFlag(fd uintptr, flags uint) error {
271+
ret := C.mdbxgo_env_copy2fd(env._env, C.uintptr_t(fd), C.MDBX_copy_flags_t(flags))
272+
return operrno("mdbx_env_copy2fd", ret)
273+
}
254274

255-
// Copy copies the data in env to an environment at path.
275+
// Copy copies the data in env as-is to an environment at path. The target
276+
// path must not already exist; pass CopyOverwrite via CopyFlag to overwrite.
256277
//
257278
// See mdbx_env_copy.
258-
// func (env *Env) Copy(path string) error {
259-
// cpath := C.CString(path)
260-
// defer C.free(unsafe.Pointer(cpath))
261-
// ret := C.mdbx_env_copy(env._env, cpath)
262-
// return operrno("mdbx_env_copy", ret)
263-
//}
264-
265-
// CopyFlag copies the data in env to an environment at path created with flags.
266-
//
267-
// See mdbx_env_copy2.
268-
// func (env *Env) CopyFlag(path string, flags uint) error {
269-
// cpath := C.CString(path)
270-
// defer C.free(unsafe.Pointer(cpath))
271-
// ret := C.mdbx_env_copy2(env._env, cpath, C.uint(flags))
272-
// return operrno("mdbx_env_copy2", ret)
273-
//}
279+
func (env *Env) Copy(path string) error {
280+
return env.CopyFlag(path, CopyDefaults)
281+
}
282+
283+
// CopyFlag copies the data in env to an environment at path, with options.
284+
//
285+
// See mdbx_env_copy.
286+
func (env *Env) CopyFlag(path string, flags uint) error {
287+
cpath := C.CString(path)
288+
defer C.free(unsafe.Pointer(cpath))
289+
ret := C.mdbx_env_copy(env._env, cpath, C.MDBX_copy_flags_t(flags))
290+
return operrno("mdbx_env_copy", ret)
291+
}
274292

275293
// Stat contains database status information.
276294
//
@@ -686,3 +704,82 @@ func (env *Env) CloseDBI(db DBI) {
686704
func (env *Env) CHandle() unsafe.Pointer {
687705
return unsafe.Pointer(env._env)
688706
}
707+
708+
// DefragOptions controls Env.Defrag. Counts are pages, not bytes. Zero means
709+
// "no bound" for every field.
710+
//
711+
// See mdbx_env_defrag.
712+
type DefragOptions struct {
713+
DefragAtLeast uint64 // shrink by at least this many pages; must be <= DefragEnough
714+
TimeAtLeast time.Duration // keep going at least this long; must be <= TimeLimit
715+
DefragEnough uint64 // stop once shrunk by this many pages
716+
TimeLimit time.Duration // stop after this long
717+
// AcceptableBacklash stops defrag once a further cycle would gain no more
718+
// than this many pages. -1 selects autopilot. libmdbx silently clamps it
719+
// to one GC overflow page of page numbers, ~1018 at a 4KiB page size, so
720+
// larger values all behave alike.
721+
AcceptableBacklash int64
722+
PreferredBatch int64 // preferred max pages moved per cycle
723+
}
724+
725+
// DefragResult holds the metrics returned by Env.Defrag.
726+
//
727+
// See MDBX_defrag_result_t.
728+
type DefragResult struct {
729+
PagesShrunk int64 // Pages the file shrank by; negative if it could not shrink.
730+
PagesMoved uint64 // Total pages moved during defragmentation.
731+
PagesScheduled uint64 // Pages scheduled to move at the next stage of the current cycle.
732+
PagesRetained uint64 // Pages held by other processes via MVCC-snapshots.
733+
PagesLeft uint64 // Estimated remaining defragmentable pages.
734+
PagesWhole uint64 // Total number of pages in the database.
735+
ObstructedPgNo uint64 // Page where defragmentation stumbled.
736+
ObstructedSpan uint64 // Length of the large/overflow-page span where it stumbled.
737+
ObstructedTxnID uint64 // Earliest MVCC-snapshot txnid preventing defragmentation.
738+
ObstructorTID uint64 // Native TID of one of the blocking readers.
739+
ObstructorPID int64 // Native PID of one of the blocking readers.
740+
CycleProgress uint // Rough estimate of current cycle progress in permilles (1000 = 100%).
741+
Cycles uint // Number of defragmentation cycles performed.
742+
StoppingReasons uint // OR'ed mask of Defrag* stopping reasons.
743+
SpentTime time.Duration
744+
}
745+
746+
// Defrag defragments the database in place: pages near the end of the file
747+
// are moved into free pages nearer the beginning, then the trailing free
748+
// pages are cut off. It is ACID and runs in several committed cycles.
749+
//
750+
// Open the environment with Exclusive: cutting the tail needs the whole-file
751+
// lock, and without it Windows fails the shrink with ERROR_LOCK_VIOLATION.
752+
//
753+
// The result is non-nil even on error. Reaching the requested goals only
754+
// partly is not an error; read result.StoppingReasons for the reason. err can
755+
// be LaggardReader, which also means "stopped early" rather than "failed".
756+
//
757+
// See mdbx_env_defrag.
758+
func (env *Env) Defrag(opts DefragOptions) (*DefragResult, error) {
759+
r := C.mdbxgo_env_defrag(env._env,
760+
C.size_t(opts.DefragAtLeast),
761+
C.size_t(NewDuration16dot16(opts.TimeAtLeast)),
762+
C.size_t(opts.DefragEnough),
763+
C.size_t(NewDuration16dot16(opts.TimeLimit)),
764+
C.intptr_t(opts.AcceptableBacklash),
765+
C.intptr_t(opts.PreferredBatch),
766+
)
767+
res := &DefragResult{
768+
PagesShrunk: int64(r.pages_shrunk),
769+
PagesMoved: uint64(r.pages_moved),
770+
PagesScheduled: uint64(r.pages_scheduled),
771+
PagesRetained: uint64(r.pages_retained),
772+
PagesLeft: uint64(r.pages_left),
773+
PagesWhole: uint64(r.pages_whole),
774+
ObstructedPgNo: uint64(r.obstructed_pgno),
775+
ObstructedSpan: uint64(r.obstructed_span),
776+
ObstructedTxnID: uint64(r.obstructed_txnid),
777+
ObstructorTID: uint64(r.obstructor_tid),
778+
ObstructorPID: int64(r.obstructor_pid),
779+
CycleProgress: uint(r.rough_estimation_cycle_progress_permille),
780+
Cycles: uint(r.cycles),
781+
StoppingReasons: uint(r.stopping_reasons),
782+
SpentTime: toDurationU64(r.spent_time_dot16),
783+
}
784+
return res, operrno("mdbx_env_defrag", r.err)
785+
}

0 commit comments

Comments
 (0)