@@ -190,79 +190,93 @@ func (s *SMT) Commit(unsortedOps map[uint64]valueOp) (err lib.ErrorI) {
190190
191191// CommitParallel() executes deferred operations in parallel by partitioning them into
192192// 8 subtrees based on their 3-bit prefix (000-111), avoiding conflicts between operations
193- // that would modify overlapping tree regions. Each subtree is processed independently,
194- // then the results are merged back into the main tree.
193+ // that would modify overlapping tree regions. Each subtree is processed independently
194+ // with its own Txn copy to avoid lock contention, then the results are merged back into
195+ // the main tree.
195196func (s * SMT ) CommitParallel (unsortedOps map [uint64 ]valueOp ) (err lib.ErrorI ) {
196197 // if there are too few operations, fall back to sequential processing
197198 // if len(unsortedOps) < NumSubtrees*2 {
198199 // return s.Commit(unsortedOps)
199200 // }
200201
201- // sort operations by their 3-bit prefix to avoid conflicts
202+ parentTxn , ok := s .store .(* Txn )
203+ if ! ok {
204+ return s .Commit (unsortedOps )
205+ }
206+
202207 groups , err := s .sortOperationsByPrefix (unsortedOps )
203208 if err != nil {
204209 return err
205210 }
206211
207- // add synthetic borders to enable safe parallel processing
208212 cleanup , err := s .addSyntheticBorders ()
209213 if err != nil {
210214 return err
211215 }
212216 defer func () {
213- // cleanup synthetic borders regardless of success/failure
214217 if cleanupErr := cleanup (); cleanupErr != nil && err == nil {
215218 err = cleanupErr
216219 }
217220 }()
218221
219- // get subtree roots for parallel processing
220222 subtreeRoots , err := s .getSubtreeRoots ()
221223 if err != nil {
222224 return err
223225 }
224226
225- // process subtrees in parallel using goroutines
226227 type subtreeResult struct {
227228 index int
229+ store * subtreeStore
228230 err lib.ErrorI
229231 }
230232
231233 resultChan := make (chan subtreeResult , NumSubtrees )
232234 activeSubtrees := 0
233235
234- // launch goroutines for each subtree that has operations
235236 for i := 0 ; i < NumSubtrees ; i ++ {
236237 if len (groups [i ]) == 0 {
237- continue // skip empty groups
238+ continue
238239 }
239-
240240 activeSubtrees ++
241- go func (idx int , ops []* node , root * node ) {
242- // create an isolated subtree for this prefix
243- subtree := s .createSubtree (root , ops )
244- // reset subtree state for processing
241+ st := parentTxn .newSubtreeStore ()
242+
243+ go func (idx int , ops []* node , root * node , store * subtreeStore ) {
244+ subtree := & SMT {
245+ store : store ,
246+ root : root ,
247+ keyBitLength : s .keyBitLength ,
248+ nodeCache : make (map [string ]* node ),
249+ operations : ops ,
250+ minKey : s .minKey ,
251+ maxKey : s .maxKey ,
252+ }
245253 subtree .reset ()
246- // process operations in this subtree
247- err := subtree .commit (true )
248- // send result back
249- resultChan <- subtreeResult {index : idx , err : err }
250- }(i , groups [i ], subtreeRoots [i ])
254+ commitErr := subtree .commit (true )
255+ resultChan <- subtreeResult {
256+ index : idx ,
257+ store : store ,
258+ err : commitErr ,
259+ }
260+ }(i , groups [i ], subtreeRoots [i ], st )
251261 }
252262
253- // collect results from all active subtrees
263+ results := make ([] subtreeResult , 0 , activeSubtrees )
254264 for completed := 0 ; completed < activeSubtrees ; completed ++ {
255265 result := <- resultChan
256266 if result .err != nil {
257- // if any subtree fails, we need to return the error
258- // the cleanup function will handle synthetic border removal
267+ // Drain remaining results before returning so goroutines
268+ // finish and don't race with the deferred cleanup.
269+ for completed ++ ; completed < activeSubtrees ; completed ++ {
270+ <- resultChan
271+ }
259272 return result .err
260273 }
274+ results = append (results , result )
275+ }
276+ for _ , result := range results {
277+ parentTxn .mergeSubtreeOps (result .store )
261278 }
262279
263- // after all subtrees are processed, the tree state is already consistent
264- // because each subtree operation updated the shared store
265- // we just need to refresh our in-memory view of the root
266280 s .root , err = s .getNode (s .root .Key .bytes ())
267281 if err != nil {
268282 return err
@@ -547,19 +561,6 @@ func (s *SMT) getSubtreeRoots() (roots []*node, err lib.ErrorI) {
547561 return
548562}
549563
550- // createSubtree() initializes the subtree structure
551- func (s * SMT ) createSubtree (root * node , operations []* node ) * SMT {
552- return & SMT {
553- store : s .store ,
554- root : root ,
555- keyBitLength : s .keyBitLength ,
556- nodeCache : make (map [string ]* node ),
557- operations : operations ,
558- minKey : s .minKey ,
559- maxKey : s .maxKey ,
560- }
561- }
562-
563564// sortOperationsByPrefix returns 8 sorted slices grouped by 3-bit prefix: 000 to 111
564565func (s * SMT ) sortOperationsByPrefix (unsortedOps map [uint64 ]valueOp ) (groups [8 ][]* node , err lib.ErrorI ) {
565566 // for each unsorted operation
0 commit comments