@@ -708,6 +708,20 @@ Search::deleteTagsPrev()
708708 for (TagGroup** tag_groups: tag_groups_prev_)
709709 delete [] tag_groups;
710710 tag_groups_prev_.clear ();
711+
712+ deletePendingPaths ();
713+ }
714+
715+ // Free old vertex path arrays that were deferred during parallel BFS visits.
716+ // Called after visitParallel completes so no thread can still hold a pointer
717+ // into any of these arrays.
718+ void
719+ Search::deletePendingPaths ()
720+ {
721+ LockGuard lock (paths_pending_delete_lock_);
722+ for (Path *paths : paths_pending_delete_)
723+ delete [] paths;
724+ paths_pending_delete_.clear ();
711725}
712726
713727void
@@ -2815,8 +2829,28 @@ void
28152829Search::setVertexArrivals (Vertex *vertex,
28162830 TagGroupBldr *tag_bldr)
28172831{
2818- if (tag_bldr->empty ())
2819- deletePathsIncr (vertex);
2832+ if (tag_bldr->empty ()) {
2833+ // Inline the deletePathsIncr logic using deferred deletion so that
2834+ // concurrent CRPR/latch readers that hold a pointer into the old path
2835+ // array are not left with a dangling pointer.
2836+ tnsNotifyBefore (vertex);
2837+ if (worst_slacks_)
2838+ worst_slacks_->worstSlackNotifyBefore (vertex);
2839+ TagGroup *tag_group = tagGroup (vertex);
2840+ if (tag_group) {
2841+ Path *old_paths = vertex->paths ();
2842+ // Clear the tag group index first so concurrent readers observe a
2843+ // null tag group (and return early from Path::vertexPath) before
2844+ // we touch paths_.
2845+ vertex->setTagGroupIndex (tag_group_index_max);
2846+ vertex->setPathsDeferred (nullptr );
2847+ tag_group->decrRefCount ();
2848+ if (old_paths) {
2849+ LockGuard lock (paths_pending_delete_lock_);
2850+ paths_pending_delete_.push_back (old_paths);
2851+ }
2852+ }
2853+ }
28202854 else {
28212855 TagGroup *prev_tag_group = tagGroup (vertex);
28222856 Path *prev_paths = vertex->paths ();
@@ -2827,13 +2861,25 @@ Search::setVertexArrivals(Vertex *vertex,
28272861 }
28282862 else {
28292863 if (prev_tag_group) {
2830- vertex->deletePaths ();
2864+ // Clear the tag group index before replacing paths so concurrent
2865+ // readers see a consistent null-tag-group state during the
2866+ // transition and do not mix the old tag group with the new array.
2867+ vertex->setTagGroupIndex (tag_group_index_max);
28312868 prev_tag_group->decrRefCount ();
28322869 requiredInvalid (vertex);
28332870 }
28342871 size_t path_count = tag_group->pathCount ();
2835- Path *paths = vertex->makePaths (path_count);
2836- tag_bldr->copyPaths (tag_group, paths);
2872+ // Allocate the new array and switch paths_ directly from old to new
2873+ // without creating a null window or freeing the old array immediately.
2874+ // This prevents concurrent CRPR/latch readers from observing either a
2875+ // null pointer or a freed (dangling) pointer.
2876+ Path *new_paths = new Path[path_count];
2877+ vertex->setPathsDeferred (new_paths);
2878+ if (prev_paths) {
2879+ LockGuard lock (paths_pending_delete_lock_);
2880+ paths_pending_delete_.push_back (prev_paths);
2881+ }
2882+ tag_bldr->copyPaths (tag_group, new_paths);
28372883 vertex->setTagGroupIndex (tag_group->index ());
28382884 tag_group->incrRefCount ();
28392885 }
0 commit comments