zeroshade commented on code in PR #864:
URL: https://github.com/apache/arrow-go/pull/864#discussion_r3554372419


##########
parquet/metadata/bloom_filter_test.go:
##########
@@ -721,3 +723,23 @@ func TestGetSpacedHashesFromBitmap(t *testing.T) {
                assert.Equal(t, int(numValid), trueCount+falseCount, "all 
hashes should be true or false")
        })
 }
+
+func TestRecycleEncryptedPathPool(t *testing.T) {
+       pool := &sync.Pool{New: func() any { return 
memory.NewResizableBuffer(memory.NewGoAllocator()) }}
+       r := &RowGroupBloomFilterReader{bufferPool: pool}
+
+       bitset := make([]byte, 1024)
+       bf := &blockSplitBloomFilter{
+               data:     memory.NewBufferBytes(bitset),
+               bitset32: arrow.Uint32Traits.CastFromBytes(bitset),
+       }
+
+       r.recycle(bf)
+
+       got := pool.Get().(*memory.Buffer)
+       if got.Mutable() {

Review Comment:
   Non-blocking: this guard is non-deterministic. `sync.Pool.Get()` may hand 
back a freshly `New()`-ed (mutable) buffer even if `recycle` had wrongly pooled 
the nil-allocator one, in which case this early-`return`s and the test passes 
without ever hitting the `ResizeNoShrink` panic path. With the current 
(correct) code it always passes, so as a regression guard it's weak. Consider 
asserting deterministically that `recycle` did not enqueue the encrypted buffer 
— e.g. recycle into a freshly-created empty pool and assert the buffer it holds 
is the allocator-backed one (`Mutable()`), or compare the specific pointer.
   



##########
parquet/metadata/bloom_filter.go:
##########
@@ -549,6 +551,46 @@ func (r *RowGroupBloomFilterReader) GetColumnBloomFilter(i 
int) (BloomFilter, er
        return bf, nil
 }
 
+// VisitColumnBloomFilter invokes fn for the BloomFilter of the column at 
index i.
+//
+// Lifetime contract: The BloomFilter passed to fn (and its backing bitset)
+// is recycled immediately after fn returns. Callers must not retain, store,
+// or use the BloomFilter or its data outside the scope of the fn function.
+func (r *RowGroupBloomFilterReader) VisitColumnBloomFilter(i int, fn 
func(BloomFilter) error) error {
+       bf, err := r.GetColumnBloomFilter(i)
+       if err != nil || bf == nil {
+               return err
+       }
+
+       defer r.recycle(bf)
+
+       return fn(bf)
+}
+
+func (r *RowGroupBloomFilterReader) recycle(bf BloomFilter) {
+       if bf == nil {
+               return
+       }
+
+       b, ok := bf.(*blockSplitBloomFilter)
+       if !ok {
+               return
+       }
+
+       b.bitset32 = nil
+
+       if r.bufferPool != nil && b.cancelCleanup != nil {
+               // Stop the GC cleanup so it can't return b.data to the pool a 
second time.
+               b.cancelCleanup()

Review Comment:
   This resolves the double-`Put` blocker from my last round — verified it 
unregisters in both build variants (`c.Stop` on go1.24, 
`runtime.SetFinalizer(bf, nil)` on ≤1.23), so the buffer can't be returned to 
the pool a second time.
   
   Non-blocking: there's still no dedicated regression test for this path. 
`TestRecycleEncryptedPathPool` only exercises the encrypted 
(nil-`cancelCleanup`) branch, not the pool-backed one. A test that 
reads/recycles a *pool-backed* filter, forces `runtime.GC()`, and asserts the 
same buffer isn't enqueued twice (e.g. two `pool.Get()`s return distinct 
pointers) would lock this in — the original bug was CI-invisible since it only 
fires after a GC and the suite doesn't run this under `-race`.
   



##########
parquet/metadata/bloom_filter.go:
##########
@@ -549,6 +551,46 @@ func (r *RowGroupBloomFilterReader) GetColumnBloomFilter(i 
int) (BloomFilter, er
        return bf, nil
 }
 
+// VisitColumnBloomFilter invokes fn for the BloomFilter of the column at 
index i.
+//
+// Lifetime contract: The BloomFilter passed to fn (and its backing bitset)
+// is recycled immediately after fn returns. Callers must not retain, store,
+// or use the BloomFilter or its data outside the scope of the fn function.
+func (r *RowGroupBloomFilterReader) VisitColumnBloomFilter(i int, fn 
func(BloomFilter) error) error {

Review Comment:
   Nice scoped accessor — the lifetime contract is clear and `recycle` nils 
`bitset32`/`data` so misuse fails fast. Two non-blocking follow-up notes:
   
   1. Nothing in the library calls `VisitColumnBloomFilter` yet, so the 
prompt-return win only lands for callers who opt in; `GetColumnBloomFilter` 
users still rely on GC recycling. Fine if that's the intent.
   2. The bloom reader still shares `file.Reader.bufferPool` with the 
column/record readers, so MB-sized bitsets and small page buffers churn the 
same pool. A dedicated per-reader bloom pool would resolve that pollution — 
good as a follow-up, not needed here.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to