cmcfarlen commented on code in PR #13616:
URL: https://github.com/apache/trafficserver/pull/13616#discussion_r4008235819


##########
include/tsutil/Metrics.h:
##########
@@ -231,43 +299,80 @@ class Metrics
       return std::make_tuple(name, type, metric->_value.load());
     }
 
+    /** Equality.
+     *
+     * Three way rather than a plain position compare: any exhausted iterator 
equals the end
+     * sentinel, and equals any other exhausted iterator, since two of them 
may have skipped a
+     * different number of unlisted slots. Two live iterators still compare by 
position.
+     *
+     * Two positional iterators may hold different snapshots, so exhaustion 
between them is judged
+     * against the earlier bound. Otherwise a walk could pass its own bound 
while a stop iterator
+     * made later was still live: they would never compare equal and @c 
operator++ could not make
+     * progress. The sentinel keeps its own answer, since its bound is 
meaningless.
+     *
+     * @note A snapshot is the sequence: iterators from different ones are no 
more comparable than
+     *   iterators into different containers, and mixing them is unspecified. 
Within one snapshot
+     *   equality is the equivalence relation an input iterator requires. The 
rule above keeps the
+     *   unspecified case terminating rather than hanging.
+     */
     bool
     operator==(const iterator &o) const
     {
-      return _it == o._it && std::addressof(_metrics) == 
std::addressof(o._metrics);
-    }
+      if (std::addressof(_metrics) != std::addressof(o._metrics)) {
+        return false;
+      }
 
-    bool
-    operator!=(const iterator &o) const
-    {
-      return _it != o._it || std::addressof(_metrics) != 
std::addressof(o._metrics);
+      if (_end || o._end) {
+        return at_end() == o.at_end();
+      }
+
+      auto const bound = _bound < o._bound ? _bound : o._bound;

Review Comment:
   Reproduced, and it goes one step further than described. With `a`, `b`, `c` 
listed, `start = find("a")`, `stop = find("b")`, then `unlist("b")`: the walk 
visits `a`, skips the unlisted `b` and lands on `c`, compares live-vs-live so 
it visits `c` outside the range, then exhausts. At that point `at_end()` is 
true for the walker and false for the saved `stop`, so `a && b` is false and 
`!=` stays true while `++` cannot make progress -- it spins, dereferencing `_it 
== _bound`. `Storage::lookup` guards with `offset > _cur_off`, so one-past-end 
slips past the redirect and yields an empty name rather than `bad_id`. So: 
out-of-range visit followed by a non-terminating loop.
   
   Your reading of the coverage was right too -- the subrange test only 
unlisted interior slots, and only before obtaining the iterators. Neither test 
unlisted an endpoint after capturing it.
   
   I went with the second of your two options, but by construction rather than 
by contract: as of f340f14 there is no way to form a subrange. 
`Metrics::for_each(func)` is the only enumeration, always the whole store, so 
there is no cursor to save across a listing change and no invalidation rule to 
define or enforce. `_bound`, the shared-bound comparison and the equality rules 
are all gone with the iterator. The two subrange tests are deleted rather than 
extended, since what they covered can no longer be expressed.
   
   Net effect on the PR is 170 insertions against 344 deletions.



-- 
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