@cryptotaxi247 / kubo / commits / 904736c20

fix(mfs): add soft limit for `--flush=false` (#10985)

* fix: add MFS operation limit for --flush=false adds a global counter that tracks consecutive MFS operations performed with --flush=false and fails with clear error after limit is reached. this prevents unbounded memory growth while avoiding the data corruption risks of auto-flushing. - adds Internal.MFSNoFlushLimit config - operations fail with actionable error at limit - counter resets on successful flush or any --flush=true operation - operations with --flush=true reset and don't count this commit removes automatic flush from https://github.com/ipfs/kubo/pull/10971 and instead errors to encourage users of --flush=false to develop a habit of calling 'ipfs files flush' periodically. boxo will no longer auto-flush (https://github.com/ipfs/boxo/pull/1041) to avoid corruption issues, and kubo applies the limit to 'ipfs files' commands instead. closes #10842 * test: add tests for MFSNoFlushLimit tests verify the new Internal.MFSNoFlushLimit config option: - default limit of 256 operations - custom limit configuration - counter reset on flush=true - counter reset on explicit flush command - limit=0 disables the feature - multiple MFS command types count towards limit * docs: explain why MFS operations fail instead of auto-flushing addresses feedback from https://github.com/ipfs/kubo/pull/10985#pullrequestreview-3256250970 - clarify that automatic flushing at limit was considered but rejected - explain the data corruption risks of auto-flushing - guide users who want auto-flush to use --flush=true (default) - document benefits of explicit failure for batch operations (cherry picked from commit a688b7eeac874f958097541058136e5b50ccd68a)

Marcin Rataj committed Sep 26, 2025 at 01:25 UTC 904736c20e68aaae8d15d2d86dbab7f6e94f9174
12 files changed +282 -50
config/internal.go
+10 -6
@@ -1,19 +1,23 @@
1 package config
2
3 +const (
4 + // DefaultMFSNoFlushLimit is the default limit for consecutive unflushed MFS operations
5 + DefaultMFSNoFlushLimit = 256
6 +)
7 +
8 type Internal struct {
9 // All marked as omitempty since we are expecting to make changes to all subcomponents of Internal
10 Bitswap *InternalBitswap `json:",omitempty"`
11 UnixFSShardingSizeThreshold *OptionalString `json:",omitempty"` // moved to Import.UnixFSHAMTDirectorySizeThreshold
12 Libp2pForceReachability *OptionalString `json:",omitempty"`
13 BackupBootstrapInterval *OptionalDuration `json:",omitempty"`
9 - // MFSAutoflushThreshold controls the number of entries cached in memory
10 - // for each MFS directory before auto-flush is triggered to prevent
11 - // unbounded memory growth when using --flush=false.
12 - // Default: 256 (matches HAMT shard size)
13 - // Set to 0 to disable cache limiting (old behavior, may cause high memory usage)
14 + // MFSNoFlushLimit controls the maximum number of consecutive
15 + // MFS operations allowed with --flush=false before requiring a manual flush.
16 + // This prevents unbounded memory growth and ensures data consistency.
17 + // Set to 0 to disable limiting (old behavior, may cause high memory usage)
18 // This is an EXPERIMENTAL feature and may change or be removed in future releases.
19 // See https://github.com/ipfs/kubo/issues/10842
16 - MFSAutoflushThreshold OptionalInteger `json:",omitempty"`
20 + MFSNoFlushLimit *OptionalInteger `json:",omitempty"`
21 }
22
23 type InternalBitswap struct {
core/commands/files.go
+68 -8
@@ -11,6 +11,8 @@ import (
11 "slices"
12 "strconv"
13 "strings"
14 + "sync"
15 + "sync/atomic"
16 "time"
17
18 humanize "github.com/dustin/go-humanize"
@@ -35,6 +37,43 @@ import (
37
38 var flog = logging.Logger("cmds/files")
39
40 +// Global counter for unflushed MFS operations
41 +var noFlushOperationCounter atomic.Int64
42 +
43 +// Cached limit value (read once on first use)
44 +var (
45 + noFlushLimit int64
46 + noFlushLimitInit sync.Once
47 +)
48 +
49 +// updateNoFlushCounter manages the counter for unflushed operations
50 +func updateNoFlushCounter(nd *core.IpfsNode, flush bool) error {
51 + if flush {
52 + // Reset counter when flushing
53 + noFlushOperationCounter.Store(0)
54 + return nil
55 + }
56 +
57 + // Cache the limit on first use (config doesn't change at runtime)
58 + noFlushLimitInit.Do(func() {
59 + noFlushLimit = int64(config.DefaultMFSNoFlushLimit)
60 + if cfg, err := nd.Repo.Config(); err == nil && cfg.Internal.MFSNoFlushLimit != nil {
61 + noFlushLimit = cfg.Internal.MFSNoFlushLimit.WithDefault(int64(config.DefaultMFSNoFlushLimit))
62 + }
63 + })
64 +
65 + // Check if limit reached
66 + if noFlushLimit > 0 && noFlushOperationCounter.Load() >= noFlushLimit {
67 + return fmt.Errorf("reached limit of %d unflushed MFS operations. "+
68 + "To resolve: 1) run 'ipfs files flush' to persist changes, "+
69 + "2) use --flush=true (default), or "+
70 + "3) increase Internal.MFSNoFlushLimit in config", noFlushLimit)
71 + }
72 +
73 + noFlushOperationCounter.Add(1)
74 + return nil
75 +}
76 +
77 // FilesCmd is the 'ipfs files' command
78 var FilesCmd = &cmds.Command{
79 Helptext: cmds.HelpText{
@@ -68,12 +107,14 @@ of consistency guarantees. If the daemon is unexpectedly killed before running
107 'ipfs files flush' on the files in question, then data may be lost. This also
108 applies to run 'ipfs repo gc' concurrently with '--flush=false' operations.
109
71 -When using '--flush=false', directories will automatically flush when the
72 -number of cached entries exceeds the Internal.MFSAutoflushThreshold config.
73 -This prevents unbounded memory growth. We recommend flushing
74 -paths regularly with 'ipfs files flush', specially the folders on which many
75 -write operations are happening, as a way to clear the directory cache, free
76 -memory and speed up read operations.`,
110 +When using '--flush=false', operations are limited to prevent unbounded
111 +memory growth. After reaching Internal.MFSNoFlushLimit operations, further
112 +operations will fail until you run 'ipfs files flush'. This explicit failure
113 +(instead of auto-flushing) ensures you maintain control over when data is
114 +persisted, preventing unexpected partial states and making batch operations
115 +predictable. We recommend flushing paths regularly, especially folders with
116 +many write operations, to clear caches, free memory, and maintain good
117 +performance.`,
118 },
119 Options: []cmds.Option{
120 cmds.BoolOption(filesFlushOptionName, "f", "Flush target and ancestors after write.").WithDefault(true),
@@ -516,12 +557,16 @@ being GC'ed.
557 }
558 }
559
560 + flush, _ := req.Options[filesFlushOptionName].(bool)
561 +
562 + if err := updateNoFlushCounter(nd, flush); err != nil {
563 + return err
564 + }
565 +
566 err = mfs.PutNode(nd.FilesRoot, dst, node)
567 if err != nil {
568 return fmt.Errorf("cp: cannot put node in path %s: %s", dst, err)
569 }
523 -
524 - flush, _ := req.Options[filesFlushOptionName].(bool)
570 if flush {
571 if _, err := mfs.FlushPath(req.Context, nd.FilesRoot, dst); err != nil {
572 return fmt.Errorf("cp: cannot flush the created file %s: %s", dst, err)
@@ -847,6 +892,10 @@ Example:
892
893 flush, _ := req.Options[filesFlushOptionName].(bool)
894
895 + if err := updateNoFlushCounter(nd, flush); err != nil {
896 + return err
897 + }
898 +
899 src, err := checkPath(req.Arguments[0])
900 if err != nil {
901 return err
@@ -984,6 +1033,10 @@ See '--to-files' in 'ipfs add --help' for more information.
1033 flush, _ := req.Options[filesFlushOptionName].(bool)
1034 rawLeaves, rawLeavesDef := req.Options[filesRawLeavesOptionName].(bool)
1035
1036 + if err := updateNoFlushCounter(nd, flush); err != nil {
1037 + return err
1038 + }
1039 +
1040 if !rawLeavesDef && cfg.Import.UnixFSRawLeaves != config.Default {
1041 rawLeavesDef = true
1042 rawLeaves = cfg.Import.UnixFSRawLeaves.WithDefault(config.DefaultUnixFSRawLeaves)
@@ -1112,6 +1165,10 @@ Examples:
1165
1166 flush, _ := req.Options[filesFlushOptionName].(bool)
1167
1168 + if err := updateNoFlushCounter(n, flush); err != nil {
1169 + return err
1170 + }
1171 +
1172 prefix, err := getPrefix(req)
1173 if err != nil {
1174 return err
@@ -1164,6 +1221,9 @@ are run with the '--flush=false'.
1221 return err
1222 }
1223
1224 + // Reset the counter (flush always resets)
1225 + noFlushOperationCounter.Store(0)
1226 +
1227 return cmds.EmitOnce(res, &flushRes{enc.Encode(n.Cid())})
1228 },
1229 Type: flushRes{},
core/node/core.go
-7
@@ -246,13 +246,6 @@ func Files(strategy string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, repo
246 return nil, err
247 }
248
249 - // Configure MFS directory cache auto-flush threshold if specified (experimental)
250 - cfg, err := repo.Config()
251 - if err == nil && !cfg.Internal.MFSAutoflushThreshold.IsDefault() {
252 - threshold := int(cfg.Internal.MFSAutoflushThreshold.WithDefault(int64(mfs.DefaultMaxCacheSize)))
253 - root.SetMaxCacheSize(threshold)
254 - }
255 -
249 lc.Append(fx.Hook{
250 OnStop: func(ctx context.Context) error {
251 return root.Close()
docs/changelogs/v0.38.md
+6 -5
@@ -17,7 +17,8 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
17 - [🎨 Updated WebUI](#-updated-webui)
18 - [📌 Pin name improvements](#-pin-name-improvements)
19 - [🛠️ Identity CID size enforcement and `ipfs files write` fixes](#️-identity-cid-size-enforcement-and-ipfs-files-write-fixes)
20 - - [Fix: Provide Filestore and Urlstore blocks on write](#️-provide-filestore-and-urlstore-blocks-on-write)
20 + - [📤 Provide Filestore and Urlstore blocks on write](#-provide-filestore-and-urlstore-blocks-on-write)
21 + - [🚦 MFS operation limit for --flush=false](#-mfs-operation-limit-for---flush=false)
22 - [📦️ Important dependency updates](#-important-dependency-updates)
23 - [📝 Changelog](#-changelog)
24 - [👨‍👩‍👧‍👦 Contributors](#-contributors)
@@ -108,13 +109,13 @@ Identity CIDs use [multihash `0x00`](https://github.com/multiformats/multicodec/
109
110 This release resolves several long-standing MFS issues: raw nodes now preserve their codec instead of being forced to dag-pb, append operations on raw nodes work correctly by converting to UnixFS when needed, and identity CIDs properly inherit the full CID prefix from parent directories.
111
111 -#### Provide Filestore and Urlstore blocks on write
112 +#### 📤 Provide Filestore and Urlstore blocks on write
113
113 -Improvements to the providing system in the last release (provide blocks according to the configured Strategy) left out [Filestore](https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-filestore) and [Urlstore](https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-urlstore) blocks when the "all" strategy was used. They would only be reprovided but not provided on write. This is now fixed, and both Filestore blocks (local file references) and Urlstore blocks (HTTP/HTTPS URL references) will be provided correctly shortly after initial add.
114 +Improvements to the providing system in the last release (provide blocks according to the configured [Strategy](https://github.com/ipfs/kubo/blob/master/docs/config.md#providestrategy)) left out [Filestore](https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-filestore) and [Urlstore](https://github.com/ipfs/kubo/blob/master/docs/experimental-features.md#ipfs-urlstore) blocks when the "all" strategy was used. They would only be reprovided but not provided on write. This is now fixed, and both Filestore blocks (local file references) and Urlstore blocks (HTTP/HTTPS URL references) will be provided correctly shortly after initial add.
115
115 -#### MFS directory cache auto-flush
116 +#### 🚦 MFS operation limit for --flush=false
117
117 -The new [`Internal.MFSAutoflushThreshold`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalmfsautoflushthreshold) configuration option prevents unbounded memory growth when using `--flush=false` with `ipfs files` commands by automatically flushing directories when their cache exceeds the configured threshold (default: 256 entries).
118 +The new [`Internal.MFSNoFlushLimit`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalmfsnoflushlimit) configuration option prevents unbounded memory growth when using `--flush=false` with `ipfs files` commands. After performing the configured number of operations without flushing (default: 256), further operations will fail with a clear error message instructing users to flush manually.
119
120 ### 📦️ Important dependency updates
121
docs/config.md
+28 -15
@@ -1599,27 +1599,40 @@ Type: `flag`
1599
1600 **MOVED:** see [`Import.UnixFSHAMTDirectorySizeThreshold`](#importunixfshamtdirectorysizethreshold)
1601
1602 -### `Internal.MFSAutoflushThreshold`
1602 +### `Internal.MFSNoFlushLimit`
1603
1604 -Controls the number of entries cached in memory for each MFS directory before
1605 -auto-flush is triggered to prevent unbounded memory growth when using `--flush=false`
1606 -with `ipfs files` commands.
1604 +Controls the maximum number of consecutive MFS operations allowed with `--flush=false`
1605 +before requiring a manual flush. This prevents unbounded memory growth and ensures
1606 +data consistency when using deferred flushing with `ipfs files` commands.
1607
1608 -When a directory's cache reaches this threshold, it will automatically flush to
1609 -the blockstore even when `--flush=false` is specified. This prevents excessive
1610 -memory usage while still allowing performance benefits of deferred flushing for
1611 -smaller operations.
1608 +When the limit is reached, further operations will fail with an error message
1609 +instructing the user to run `ipfs files flush`, use `--flush=true`, or increase
1610 +this limit in the configuration.
1611
1613 -**Examples:**
1614 -* `256` - Default value. Provides a good balance between performance and memory usage.
1615 -* `0` - Disables cache limiting (behavior before Kubo 0.38). May cause high memory
1616 - usage with `--flush=false` on large directories.
1617 -* `1024` - Higher limit for systems with more available memory that need to perform
1618 - many operations before flushing.
1612 +**Why operations fail instead of auto-flushing:** Automatic flushing once the limit
1613 +is reached was considered but rejected because it can lead to data corruption issues
1614 +that are difficult to debug. When the system decides to flush without user knowledge, it can:
1615 +- Create partial states that violate user expectations about atomicity
1616 +- Interfere with concurrent operations in unexpected ways
1617 +- Make debugging and recovery much harder when issues occur
1618 +
1619 +By failing explicitly, users maintain control over when their data is persisted,
1620 +allowing them to:
1621 +- Batch related operations together before flushing
1622 +- Handle errors predictably at natural transaction boundaries
1623 +- Understand exactly when and why their data is written to disk
1624 +
1625 +If you expect automatic flushing behavior, simply use the default `--flush=true`
1626 +(or omit the flag entirely) instead of `--flush=false`.
1627 +
1628 +**⚠️ WARNING:** Increasing this limit or disabling it (setting to 0) can lead to:
1629 +- **Out-of-memory errors (OOM)** - Each unflushed operation consumes memory
1630 +- **Data loss** - If the daemon crashes before flushing, all unflushed changes are lost
1631 +- **Degraded performance** - Large unflushed caches slow down MFS operations
1632
1633 Default: `256`
1634
1622 -Type: `optionalInteger` (0 disables the limit, risky, may lead to errors)
1635 +Type: `optionalInteger` (0 disables the limit, strongly discouraged)
1636
1637 **Note:** This is an EXPERIMENTAL feature and may change or be removed in future releases.
1638 See [#10842](https://github.com/ipfs/kubo/issues/10842) for more information.
docs/examples/kubo-as-a-library/go.mod
+1 -1
@@ -7,7 +7,7 @@ go 1.25
7 replace github.com/ipfs/kubo => ./../../..
8
9 require (
10 - github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7
10 + github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28
11 github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12 github.com/libp2p/go-libp2p v0.43.0
13 github.com/multiformats/go-multiaddr v0.16.1
docs/examples/kubo-as-a-library/go.sum
+2 -2
@@ -289,8 +289,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
289 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
290 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
291 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
292 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7 h1:lMzsaKoUSiMmb7xdBbTG0e2Rm85jGlo7fWO/XRhEEDA=
293 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
292 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28 h1:kDoj2V7ghhLdQeQUtzr605tb6NJ4AzwRYtXFJas+Wyc=
293 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
294 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
295 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
296 github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
go.mod
+1 -1
@@ -22,7 +22,7 @@ require (
22 github.com/hashicorp/go-version v1.7.0
23 github.com/ipfs-shipyard/nopfs v0.0.14
24 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
25 - github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7
25 + github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28
26 github.com/ipfs/go-block-format v0.2.3
27 github.com/ipfs/go-cid v0.5.0
28 github.com/ipfs/go-cidutil v0.1.0
go.sum
+2 -2
@@ -356,8 +356,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
356 github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
357 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
358 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
359 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7 h1:lMzsaKoUSiMmb7xdBbTG0e2Rm85jGlo7fWO/XRhEEDA=
360 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
359 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28 h1:kDoj2V7ghhLdQeQUtzr605tb6NJ4AzwRYtXFJas+Wyc=
360 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
361 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
362 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
363 github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
test/cli/files_test.go
+161
@@ -6,6 +6,7 @@ import (
6 "path/filepath"
7 "testing"
8
9 + "github.com/ipfs/kubo/config"
10 "github.com/ipfs/kubo/test/cli/harness"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
@@ -178,3 +179,163 @@ func TestFilesRm(t *testing.T) {
179 assert.NotContains(t, lsRes.Stdout.String(), "test-dir")
180 })
181 }
182 +
183 +func TestFilesNoFlushLimit(t *testing.T) {
184 + t.Parallel()
185 +
186 + t.Run("reaches default limit of 256 operations", func(t *testing.T) {
187 + t.Parallel()
188 + node := harness.NewT(t).NewNode().Init().StartDaemon()
189 +
190 + // Perform 256 operations with --flush=false (should succeed)
191 + for i := 0; i < 256; i++ {
192 + res := node.IPFS("files", "mkdir", "--flush=false", fmt.Sprintf("/dir%d", i))
193 + assert.NoError(t, res.Err, "operation %d should succeed", i+1)
194 + }
195 +
196 + // 257th operation should fail
197 + res := node.RunIPFS("files", "mkdir", "--flush=false", "/dir256")
198 + require.NotNil(t, res.ExitErr, "command should have failed")
199 + assert.NotEqual(t, 0, res.ExitErr.ExitCode())
200 + assert.Contains(t, res.Stderr.String(), "reached limit of 256 unflushed MFS operations")
201 + assert.Contains(t, res.Stderr.String(), "run 'ipfs files flush'")
202 + assert.Contains(t, res.Stderr.String(), "use --flush=true")
203 + assert.Contains(t, res.Stderr.String(), "increase Internal.MFSNoFlushLimit")
204 + })
205 +
206 + t.Run("custom limit via config", func(t *testing.T) {
207 + t.Parallel()
208 + node := harness.NewT(t).NewNode().Init()
209 +
210 + // Set custom limit to 5
211 + node.UpdateConfig(func(cfg *config.Config) {
212 + limit := config.NewOptionalInteger(5)
213 + cfg.Internal.MFSNoFlushLimit = limit
214 + })
215 +
216 + node.StartDaemon()
217 +
218 + // Perform 5 operations (should succeed)
219 + for i := 0; i < 5; i++ {
220 + res := node.IPFS("files", "mkdir", "--flush=false", fmt.Sprintf("/dir%d", i))
221 + assert.NoError(t, res.Err, "operation %d should succeed", i+1)
222 + }
223 +
224 + // 6th operation should fail
225 + res := node.RunIPFS("files", "mkdir", "--flush=false", "/dir5")
226 + require.NotNil(t, res.ExitErr, "command should have failed")
227 + assert.NotEqual(t, 0, res.ExitErr.ExitCode())
228 + assert.Contains(t, res.Stderr.String(), "reached limit of 5 unflushed MFS operations")
229 + })
230 +
231 + t.Run("flush=true resets counter", func(t *testing.T) {
232 + t.Parallel()
233 + node := harness.NewT(t).NewNode().Init()
234 +
235 + // Set limit to 3 for faster testing
236 + node.UpdateConfig(func(cfg *config.Config) {
237 + limit := config.NewOptionalInteger(3)
238 + cfg.Internal.MFSNoFlushLimit = limit
239 + })
240 +
241 + node.StartDaemon()
242 +
243 + // Do 2 operations with --flush=false
244 + node.IPFS("files", "mkdir", "--flush=false", "/dir1")
245 + node.IPFS("files", "mkdir", "--flush=false", "/dir2")
246 +
247 + // Operation with --flush=true should reset counter
248 + node.IPFS("files", "mkdir", "--flush=true", "/dir3")
249 +
250 + // Now we should be able to do 3 more operations with --flush=false
251 + for i := 4; i <= 6; i++ {
252 + res := node.IPFS("files", "mkdir", "--flush=false", fmt.Sprintf("/dir%d", i))
253 + assert.NoError(t, res.Err, "operation after flush should succeed")
254 + }
255 +
256 + // 4th operation after reset should fail
257 + res := node.RunIPFS("files", "mkdir", "--flush=false", "/dir7")
258 + require.NotNil(t, res.ExitErr, "command should have failed")
259 + assert.NotEqual(t, 0, res.ExitErr.ExitCode())
260 + assert.Contains(t, res.Stderr.String(), "reached limit of 3 unflushed MFS operations")
261 + })
262 +
263 + t.Run("explicit flush command resets counter", func(t *testing.T) {
264 + t.Parallel()
265 + node := harness.NewT(t).NewNode().Init()
266 +
267 + // Set limit to 3 for faster testing
268 + node.UpdateConfig(func(cfg *config.Config) {
269 + limit := config.NewOptionalInteger(3)
270 + cfg.Internal.MFSNoFlushLimit = limit
271 + })
272 +
273 + node.StartDaemon()
274 +
275 + // Do 2 operations with --flush=false
276 + node.IPFS("files", "mkdir", "--flush=false", "/dir1")
277 + node.IPFS("files", "mkdir", "--flush=false", "/dir2")
278 +
279 + // Explicit flush should reset counter
280 + node.IPFS("files", "flush")
281 +
282 + // Now we should be able to do 3 more operations
283 + for i := 3; i <= 5; i++ {
284 + res := node.IPFS("files", "mkdir", "--flush=false", fmt.Sprintf("/dir%d", i))
285 + assert.NoError(t, res.Err, "operation after flush should succeed")
286 + }
287 +
288 + // 4th operation should fail
289 + res := node.RunIPFS("files", "mkdir", "--flush=false", "/dir6")
290 + require.NotNil(t, res.ExitErr, "command should have failed")
291 + assert.NotEqual(t, 0, res.ExitErr.ExitCode())
292 + assert.Contains(t, res.Stderr.String(), "reached limit of 3 unflushed MFS operations")
293 + })
294 +
295 + t.Run("limit=0 disables the feature", func(t *testing.T) {
296 + t.Parallel()
297 + node := harness.NewT(t).NewNode().Init()
298 +
299 + // Set limit to 0 (disabled)
300 + node.UpdateConfig(func(cfg *config.Config) {
301 + limit := config.NewOptionalInteger(0)
302 + cfg.Internal.MFSNoFlushLimit = limit
303 + })
304 +
305 + node.StartDaemon()
306 +
307 + // Should be able to do many operations without error
308 + for i := 0; i < 300; i++ {
309 + res := node.IPFS("files", "mkdir", "--flush=false", fmt.Sprintf("/dir%d", i))
310 + assert.NoError(t, res.Err, "operation %d should succeed with limit disabled", i+1)
311 + }
312 + })
313 +
314 + t.Run("different MFS commands count towards limit", func(t *testing.T) {
315 + t.Parallel()
316 + node := harness.NewT(t).NewNode().Init()
317 +
318 + // Set limit to 5 for testing
319 + node.UpdateConfig(func(cfg *config.Config) {
320 + limit := config.NewOptionalInteger(5)
321 + cfg.Internal.MFSNoFlushLimit = limit
322 + })
323 +
324 + node.StartDaemon()
325 +
326 + // Mix of different MFS operations (5 operations to hit the limit)
327 + node.IPFS("files", "mkdir", "--flush=false", "/testdir")
328 + // Create a file first, then copy it
329 + testCid := node.IPFSAddStr("test content")
330 + node.IPFS("files", "cp", "--flush=false", fmt.Sprintf("/ipfs/%s", testCid), "/testfile")
331 + node.IPFS("files", "cp", "--flush=false", "/testfile", "/testfile2")
332 + node.IPFS("files", "mv", "--flush=false", "/testfile2", "/testfile3")
333 + node.IPFS("files", "mkdir", "--flush=false", "/anotherdir")
334 +
335 + // 6th operation should fail
336 + res := node.RunIPFS("files", "mkdir", "--flush=false", "/another")
337 + require.NotNil(t, res.ExitErr, "command should have failed")
338 + assert.NotEqual(t, 0, res.ExitErr.ExitCode())
339 + assert.Contains(t, res.Stderr.String(), "reached limit of 5 unflushed MFS operations")
340 + })
341 +}
test/dependencies/go.mod
+1 -1
@@ -135,7 +135,7 @@ require (
135 github.com/huin/goupnp v1.3.0 // indirect
136 github.com/inconshreveable/mousetrap v1.1.0 // indirect
137 github.com/ipfs/bbloom v0.0.4 // indirect
138 - github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7 // indirect
138 + github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28 // indirect
139 github.com/ipfs/go-bitfield v1.1.0 // indirect
140 github.com/ipfs/go-block-format v0.2.3 // indirect
141 github.com/ipfs/go-cid v0.5.0 // indirect
test/dependencies/go.sum
+2 -2
@@ -332,8 +332,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
332 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
333 github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
334 github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
335 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7 h1:lMzsaKoUSiMmb7xdBbTG0e2Rm85jGlo7fWO/XRhEEDA=
336 -github.com/ipfs/boxo v0.34.1-0.20250925094323-608486081da7/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
335 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28 h1:kDoj2V7ghhLdQeQUtzr605tb6NJ4AzwRYtXFJas+Wyc=
336 +github.com/ipfs/boxo v0.34.1-0.20250925224331-260f4b387f28/go.mod h1:uhaF0DGnbgEiXDTmD249jCGbxVkMm6+Ew85q6Uub7lo=
337 github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
338 github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
339 github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk=