Skip to content

Conversation

@PastaPastaPasta
Copy link
Member

What was done?

  • return optional, instead of ref, and bool
  • add (generated) unit tests
  • improve truncate_if_needed to use std::nth_element instead of std::sort for O(N) average complexity instead of O(N log N).

How Has This Been Tested?

Compiles

Breaking Changes

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

…onal

Change unordered_lru_cache::get() from returning bool with an output
parameter to returning std::optional<Value> by value. This provides a
more modern API that is easier to use and less error-prone.

- Add <optional> header to unordered_lru_cache.h
- Update get() signature: bool get(key, Value&) -> std::optional<Value> get(key)
- Update all 21 call sites across 11 files to use the new API
- Maintain same copy semantics for performance (no performance impact)

Files updated:
- src/unordered_lru_cache.h
- src/llmq/quorums.cpp (5 call sites)
- src/llmq/utils.cpp (2 call sites)
- src/llmq/snapshot.cpp, signing.cpp, dkgsessionmgr.cpp, blockprocessor.cpp
- src/instantsend/db.cpp (3 call sites)
- src/evo/mnhftx.cpp, creditpool.cpp, cbtx.cpp
- src/masternode/meta.cpp
Add Boost.Test suite with 17 test cases covering:
- Construction and configuration (runtime/compile-time sizing)
- Basic API behavior (insert, get, emplace, exists, erase, clear)
- LRU and truncation semantics (threshold behavior, recency tracking)
- Edge cases (maxSize=1, various threshold combinations)

Also optimize truncate_if_needed() to use std::nth_element instead
of std::sort for O(N) average complexity instead of O(N log N).
@PastaPastaPasta PastaPastaPasta added this to the 23.1 milestone Nov 12, 2025
@github-actions
Copy link

github-actions bot commented Nov 12, 2025

⚠️ Potential Merge Conflicts Detected

This PR has potential conflicts with the following open PRs:

Please coordinate with the authors of these PRs to avoid merge conflicts.

@coderabbitai
Copy link

coderabbitai bot commented Nov 12, 2025

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: improve unordered_lru_cache' directly addresses the main change: refactoring and improving the unordered_lru_cache implementation through API changes and performance optimizations.
Description check ✅ Passed The description clearly outlines what was done (return optional, add unit tests, improve truncate_if_needed), how it was tested (compiles), and includes a checklist, all directly related to the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/llmq/quorums.cpp (1)

586-591: Don’t drop already cached quorums at genesis boundary.

When vecResultQuorums.back()->m_quorum_base_block_index->pprev is nullptr we’ve hit the beginning of the chain. Returning {} here discards the quorums we already collected from the cache, so callers get an empty result even though valid quorums exist. Instead of bailing out with an empty vector, return the quorums we have (or otherwise stop scanning without erasing them).

-            if (vecResultQuorums.back()->m_quorum_base_block_index->pprev == nullptr) return {};
+            if (vecResultQuorums.back()->m_quorum_base_block_index->pprev == nullptr) {
+                return vecResultQuorums;
+            }
🧹 Nitpick comments (1)
src/unordered_lru_cache.h (1)

60-68: Consider direct return for efficiency.

Line 65 uses std::make_optional(it->second.first) which creates an extra copy. You could return the value directly:

-            return std::make_optional(it->second.first);
+            return it->second.first;

When returning from a function that returns std::optional<T>, the compiler will automatically construct the optional from the value, avoiding the intermediate copy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 368eebb and 4b85c94.

📒 Files selected for processing (14)
  • src/Makefile.test.include (1 hunks)
  • src/evo/cbtx.cpp (1 hunks)
  • src/evo/creditpool.cpp (2 hunks)
  • src/evo/mnhftx.cpp (1 hunks)
  • src/instantsend/db.cpp (3 hunks)
  • src/llmq/blockprocessor.cpp (1 hunks)
  • src/llmq/dkgsessionmgr.cpp (1 hunks)
  • src/llmq/quorums.cpp (5 hunks)
  • src/llmq/signing.cpp (3 hunks)
  • src/llmq/snapshot.cpp (1 hunks)
  • src/llmq/utils.cpp (2 hunks)
  • src/masternode/meta.cpp (1 hunks)
  • src/test/unordered_lru_cache_tests.cpp (1 hunks)
  • src/unordered_lru_cache.h (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/masternode/meta.cpp
  • src/evo/cbtx.cpp
  • src/evo/creditpool.cpp
  • src/llmq/signing.cpp
  • src/llmq/utils.cpp
  • src/llmq/snapshot.cpp
  • src/unordered_lru_cache.h
  • src/test/unordered_lru_cache_tests.cpp
  • src/llmq/blockprocessor.cpp
  • src/llmq/quorums.cpp
  • src/llmq/dkgsessionmgr.cpp
  • src/evo/mnhftx.cpp
  • src/instantsend/db.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/masternode/meta.cpp
  • src/evo/cbtx.cpp
  • src/evo/creditpool.cpp
  • src/evo/mnhftx.cpp
src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Files:

  • src/test/unordered_lru_cache_tests.cpp
🧠 Learnings (7)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp} : Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Applied to files:

  • src/Makefile.test.include
  • src/test/unordered_lru_cache_tests.cpp
📚 Learning: 2025-08-11T17:16:36.654Z
Learnt from: PastaPastaPasta
Repo: dashpay/dash PR: 6804
File: src/qt/proposalwizard.cpp:40-42
Timestamp: 2025-08-11T17:16:36.654Z
Learning: In the Dash repository, when a PR adds new files that are not from Bitcoin backports, these files must be added to the list in test/util/data/non-backported.txt. This applies to newly created files like qt/proposalwizard.{h,cpp} and forms/proposalwizard.ui. Limited exemptions may exist for subtrees and similar cases.

Applied to files:

  • src/Makefile.test.include
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/evo/cbtx.cpp
  • src/evo/creditpool.cpp
  • src/test/unordered_lru_cache_tests.cpp
  • src/instantsend/db.cpp
📚 Learning: 2025-10-02T18:29:54.756Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6840
File: src/net_processing.cpp:2882-2886
Timestamp: 2025-10-02T18:29:54.756Z
Learning: Across net_processing.cpp, once LLMQContext (m_llmq_ctx) is asserted non-null, its subcomponents (e.g., isman, qdkgsman, quorum_block_processor) are treated as initialized and used without extra null checks.

Applied to files:

  • src/evo/cbtx.cpp
  • src/llmq/quorums.cpp
  • src/llmq/dkgsessionmgr.cpp
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/evo/creditpool.cpp
  • src/llmq/blockprocessor.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/**/*.{cpp,h,cc,cxx,hpp} : Dash uses unordered_lru_cache for efficient caching with LRU eviction

Applied to files:

  • src/test/unordered_lru_cache_tests.cpp
🧬 Code graph analysis (2)
src/unordered_lru_cache.h (1)
src/script/descriptor.cpp (1)
  • nullopt (656-656)
src/llmq/dkgsessionmgr.cpp (2)
src/evo/mnhftx.h (1)
  • quorumHash (39-39)
src/llmq/commitment.h (1)
  • quorumIndex (54-54)
🪛 GitHub Actions: Clang Diff Format Check
src/llmq/utils.cpp

[error] 251-257: Clang format differences detected. The step 'git diff -U0 origin/develop -- $(git ls-files -- $(cat test/util/data/non-backported.txt)) | ./contrib/devtools/clang-format-diff.py -p1 > diff_output.txt' reported differences and exit code 1. Please run clang-format or apply the diffs to fix formatting issues.

🔇 Additional comments (10)
src/unordered_lru_cache.h (1)

105-112: LGTM! Excellent performance improvement.

The optimization from std::sort to std::nth_element is well-implemented. After partitioning, the first keep elements are guaranteed to be the most recently accessed entries, and erasing entries beyond that point is correct. This reduces the average time complexity from O(N log N) to O(N) for the eviction process.

src/instantsend/db.cpp (3)

274-278: LGTM! Clean optional-based cache lookup.

The early return pattern for cache hits is clean and efficient. The cache is consulted only when use_cache is true, and the cached value is returned immediately if present.


298-306: LGTM! Correct cache integration.

The cache lookup correctly returns early if a cached value is found. The DB read fallback and cache insertion are properly sequenced.


318-326: LGTM! Proper two-level cache lookup.

The function correctly uses the outpoint cache to retrieve the islock hash, then delegates to GetInstantSendLockByHashInternal for the full lock retrieval. The DB fallback and cache insertion logic are correct.

src/test/unordered_lru_cache_tests.cpp (1)

1-401: Excellent comprehensive test coverage!

The test suite thoroughly validates the unordered_lru_cache implementation across multiple dimensions:

  • Construction and configuration scenarios
  • Basic operations (insert, get, emplace, exists, erase, clear)
  • LRU eviction behavior and recency tracking
  • Edge cases (maxsize=1, threshold variations, multiple truncation cycles)
  • Value semantics (verifying returned values are copies)

The tests properly verify the new optional-based API and ensure correct LRU semantics.

src/Makefile.test.include (1)

199-199: LGTM! Test file properly added to build.

The new test file is correctly added to the BITCOIN_TESTS list in alphabetical order.

src/masternode/meta.cpp (1)

142-145: LGTM! Clean optional-based cache lookup.

The cache integration correctly uses the new optional API, returning the cached value if present or std::nullopt otherwise.

src/llmq/blockprocessor.cpp (1)

516-527: LGTM! Correct cache integration with proper locking.

The optional-based cache lookup is correct. The cache check is properly guarded by the lock, and the DB read fallback occurs outside the lock to minimize lock contention. The cache insertion before returning is correct.

src/llmq/snapshot.cpp (1)

355-357: LGTM! Clean optional-based cache integration.

The cache lookup correctly uses the new optional API with an early return on cache hit, followed by DB fallback and cache insertion.

src/evo/mnhftx.cpp (1)

328-330: LGTM! Correct optional-based cache lookup.

The cache integration properly uses the new optional API with an early return on cache hit. The multiple DB fallback paths with cache insertions are correctly implemented.

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is not ready yet IMO, I stopped reviewing in the middle

Also conflicts to #6963

LOCK(cache_mutex);
if (creditPoolCache.get(block_hash, pool)) {
return pool;
if (auto cached = creditPoolCache.get(block_hash)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should reduce scope of pool here

static_cast<size_t>(Params().CreditPoolPeriodBlocks()) * 2};
if (LOCK(cache_mutex); block_data_cache.get(block_index->GetBlockHash(), blockData)) {
return blockData;
if (LOCK(cache_mutex); auto cached = block_data_cache.get(block_index->GetBlockHash())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit(perf): should reduce scope of blockData here to avoid empty object creation

}
}

InstantSendLockPtr ret;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-    InstantSendLockPtr ret;
-    ret = std::make_shared<InstantSendLock>();
+     InstantSendLockPtr ret{std::make_shared<InstantSendLock>()};

if (!cached.has_value()) {
return errorHandler("Quorum not found", 0); // Don't bump score because we asked for it
}
pQuorum = *cached;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use std::optional's feature value_or

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why / how?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

I gave a 2nd look, it just shared_ptr; so, copy is quite fast, no allocation, one atomic is relatively cheap.

Though, it could be:

-        CQuorumPtr pQuorum;
-        {
-            LOCK(cs_map_quorums);
-            auto cached = mapQuorumsCache[request.GetLLMQType()].get(request.GetQuorumHash());
-            if (!cached.has_value()) {
-                return errorHandler("Quorum not found", 0); // Don't bump score because we asked for it
-            }
-            pQuorum = *cached;
+        CQuorumPtr pQuorum = WITH_LOCK(cs_map_quorums, return mapQuorumsCache[request.GetLLMQType()].get(request.GetQuorumHash())).value_or(nullptr);
+        if (pQuorum == nullptr) {
+            return errorHandler("Quorum not found", 0); // Don't bump score because we asked for it
         }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe compiler may optimize away this copy anyway as the obj was not initialized prior.

src/evo/cbtx.cpp Outdated

std::pair<uint256, int> qc_hash;
if (!qc_hashes_cached[llmqType].get(block_hash, qc_hash)) {
if (auto cached = qc_hashes_cached[llmqType].get(block_hash)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this particular case is not good anymore; here's 1 extra constructor of uint256 is called now.

once - default constructor.
After that one more in get()
and 3rd one is 'qc_hash=*cached'

Used to be only 2 constructors:

  1. default for qc_hash
  2. assignment in get()

std::optional supposed to decrease its amount, not increase!

Consider use std::optional's feature value_or

bool fCacheExists = cache.get(pindexStore->GetBlockHash(), vecResultQuorums);
if (fCacheExists) {
if (auto cached = cache.get(pindexStore->GetBlockHash())) {
vecResultQuorums = *cached;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using std::optional's feature value_or

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? / how?

Copy link
Collaborator

@knst knst Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, this place is not feasible; not easy to do due to if (...) and extra code inside

Consider vecResultQuorums = std::move(*cached)

@coderabbitai, check that std::move is working with std::optional's values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint256 is trivially copyable; there's no gain to move really

@PastaPastaPasta PastaPastaPasta requested a review from knst November 12, 2025 15:30
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/evo/cbtx.cpp (1)

95-97: Construct the cached pair in-place.
Switching to std::optional::emplace avoids creating an extra temporary std::pair<uint256, int> when we fill a miss, trimming one more uint256 construction at virtually no cost to readability.

-                qc_hash = {::SerializeHash(pqc), rotation_enabled ? pqc.quorumIndex : 0};
+                qc_hash.emplace(::SerializeHash(pqc), rotation_enabled ? pqc.quorumIndex : 0);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b85c94 and aafef95.

📒 Files selected for processing (4)
  • src/evo/cbtx.cpp (1 hunks)
  • src/evo/creditpool.cpp (3 hunks)
  • src/instantsend/db.cpp (3 hunks)
  • src/test/unordered_lru_cache_tests.cpp (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/unordered_lru_cache_tests.cpp
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/evo/cbtx.cpp
  • src/evo/creditpool.cpp
  • src/instantsend/db.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/cbtx.cpp
  • src/evo/creditpool.cpp
🧠 Learnings (4)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/evo/creditpool.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/instantsend/db.cpp
📚 Learning: 2025-09-09T21:36:11.833Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:0-0
Timestamp: 2025-09-09T21:36:11.833Z
Learning: In RawSender class (src/stats/rawsender.cpp), cs_net is the appropriate mutex for protecting socket access (m_sock) and network operations, not additional custom locks. The implementation correctly uses cs_net with GUARDED_BY annotations and EXCLUSIVE_LOCKS_REQUIRED to synchronize socket access between SendDirectly() and ReconnectThread().

Applied to files:

  • src/instantsend/db.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: linux64_multiprocess-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: win64-build / Build source
🔇 Additional comments (1)
src/instantsend/db.cpp (1)

274-291: Good call on caching null hits.
Persisting the miss in islockCache means we stop hammering LevelDB for the same absent hash, while write paths still refresh the entry once an IS-lock arrives.

Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, one test feels incomplete though

Comment on lines +112 to +113
// Erasing non-existent key is a no-op
c.erase(999);
Copy link

@UdjinM6 UdjinM6 Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably insert another element e.g. (2, 20) at the start of this test and then check that it wasn't removed neither by erase(1) above nor by erase(999) here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants