github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/list/linked_list.go (about) 1 package list 2 3 // References: 4 // Valois, John D. "Lock-free linked lists using compare-and-swap." 5 // https://dl.acm.org/doi/pdf/10.1145/224964.224988 6 // Tim Harris, "A pragmatic implementation of non-blocking linked lists" 7 // http://www.cl.cam.ac.uk/~tlh20/publications.html 8 // Maged Michael "High Performance Dynamic Lock-Free Hash Tables and List-Based Sets" 9 // http://www.research.ibm.com/people/m/michael/pubs.htm 10 // https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html 11 12 /* Pastes from JDK 13 * The base lists use a variant of the HM linked ordered set 14 * algorithm. See Tim Harris, "A pragmatic implementation of 15 * non-blocking linked lists" 16 * http://www.cl.cam.ac.uk/~tlh20/publications.html and Maged 17 * Michael "High Performance Dynamic Lock-Free Hash Tables and 18 * List-Based Sets" 19 * http://www.research.ibm.com/people/m/michael/pubs.htm. The 20 * basic idea in these lists is to mark the "next" pointers of 21 * deleted nodes when deleting to avoid conflicts with concurrent 22 * insertions, and when traversing to keep track of triples 23 * (predecessor, node, successor) in order to detect when and how 24 * to unlink these deleted nodes. 25 * 26 * Rather than using mark-bits to mark list deletions (which can 27 * be slow and space-intensive using AtomicMarkedReference), nodes 28 * use direct CAS'able next pointers. On deletion, instead of 29 * marking a pointer, they splice in another node that can be 30 * thought of as standing for a removingMarked pointer (see method 31 * unlinkNode). Using plain nodes acts roughly like "boxed" 32 * implementations of removingMarked pointers, but uses new nodes only 33 * when nodes are deleted, not for every link. This requires less 34 * space and supports faster traversal. Even if removingMarked references 35 * were better supported by JVMs, traversal using this technique 36 * might still be faster because any search need only read ahead 37 * one more node than otherwise required (to check for trailing 38 * marker) rather than unmasking mark bits or whatever on each 39 * read. 40 * 41 * This approach maintains the essential property needed in the HM 42 * algorithm of changing the next-pointer of a deleted node so 43 * that any other CAS of it will fail, but implements the idea by 44 * changing the pointer to point to a different node (with 45 * otherwise illegal null fields), not by marking it. While it 46 * would be possible to further squeeze space by defining marker 47 * nodes not to have key/value fields, it isn't worth the extra 48 * type-testing overhead. The deletion markers are rarely 49 * encountered during traversal, are easily detected via null 50 * checks that are needed anyway, and are normally quickly garbage 51 * collected. (Note that this technique would not work well in 52 * systems without garbage collection.) 53 * 54 * In addition to using deletion markers, the lists also use 55 * nullness of value fields to indicate deletion, in a style 56 * similar to typical lazy-deletion schemes. If a node's value is 57 * null, then it is considered logically deleted and ignored even 58 * though it is still reachable. 59 * 60 * Here's the sequence of events for a deletion of node n with 61 * predecessor b and successor f, initially: 62 * 63 * +------+ +------+ +------+ 64 * ... | b |------>| n |----->| f | ... 65 * +------+ +------+ +------+ 66 * 67 * 1. CAS n's value field from non-null to null. 68 * Traversals encountering a node with null value ignore it. 69 * However, ongoing insertions and deletions might still modify 70 * n's next pointer. 71 * 72 * 2. CAS n's next pointer to point to a new marker node. 73 * From this point on, no other nodes can be appended to n. 74 * which avoids deletion errors in CAS-based linked lists. 75 * 76 * +------+ +------+ +------+ +------+ 77 * ... | b |------>| n |----->|marker|------>| f | ... 78 * +------+ +------+ +------+ +------+ 79 * 80 * 3. CAS b's next pointer over both n and its marker. 81 * From this point on, no new traversals will encounter n, 82 * and it can eventually be GCed. 83 * +------+ +------+ 84 * ... | b |----------------------------------->| f | ... 85 * +------+ +------+ 86 * 87 * A failure at step 1 leads to simple retry due to a lost race 88 * with another operation. Steps 2-3 can fail because some other 89 * thread noticed during a traversal a node with null value and 90 * helped out by marking and/or unlinking. This helping-out 91 * ensures that no thread can become stuck waiting for progress of 92 * the deleting thread. 93 * 94 * Skip lists add indexing to this scheme, so that the base-level 95 * traversals start close to the locations being found, inserted 96 * or deleted -- usually base level traversals only traverse a few 97 * nodes. This doesn't change the basic algorithm except for the 98 * need to make sure base traversals start at predecessors (here, 99 * b) that are not (structurally) deleted, otherwise retrying 100 * after processing the deletion. 101 * 102 * Index levels are maintained using CAS to link and unlink 103 * successors ("right" fields). Races are allowed in index-list 104 * operations that can (rarely) fail to link in a new index node. 105 * (We can't do this of course for bits nodes.) However, even 106 * when this happens, the index lists correctly guide search. 107 * This can impact performance, but since skip lists are 108 * probabilistic anyway, the net result is that under contention, 109 * the effective "p" value may be lower than its nominal value. 110 * 111 * Index insertion and deletion sometimes require a separate 112 * traversal pass occurring after the base-level action, to add or 113 * remove index nodes. This adds to single-threaded overhead, but 114 * improves contended multithreaded performance by narrowing 115 * interference windows, and allows deletion to ensure that all 116 * index nodes will be made unreachable upon return from a public 117 * remove operation, thus avoiding unwanted garbage retention. 118 * 119 * Indexing uses skip list parameters that maintain good search 120 * performance while using sparser-than-usual indexCount: The 121 * hardwired parameters k=1, p=0.5 (see method doPut) mean that 122 * about one-quarter of the nodes have indexCount. Of those that do, 123 * half have one level, a quarter have two, and so on (see Pugh's 124 * Skip List Cookbook, sec 3.4), up to a maximum of 62 levels 125 * (appropriate for up to 2^63 elements). The expected total 126 * space requirement for a map is slightly less than for the 127 * current implementation of java.util.TreeMap. 128 * 129 * Changing the level of the index (i.e, the height of the 130 * tree-like structure) also uses CAS. Creation of an index with 131 * height greater than the current level adds a level to the head 132 * index by CAS'ing on a new top-most head. To maintain good 133 * performance after a lot of removals, deletion methods 134 * heuristically try to reduce the height if the topmost levels 135 * appear to be empty. This may encounter races in which it is 136 * possible (but rare) to reduce and "lose" a level just as it is 137 * about to contain an index (that will then never be 138 * encountered). This does no structural harm, and in practice 139 * appears to be a better option than allowing unrestrained growth 140 * of levels. 141 * 142 * This class provides concurrent-reader-style memory consistency, 143 * ensuring that read-only methods report status and/or values no 144 * staler than those holding at method entry. This is done by 145 * performing all publication and structural updates using 146 * (volatile) CAS, placing an acquireFence in a few access 147 * methods, and ensuring that linked objects are transitively 148 * acquired via dependent reads (normally once) unless performing 149 * a volatile-mode CAS operation (that also acts as an acquire and 150 * release). This form of fence-hoisting is similar to RCU and 151 * related techniques (see McKenney's online book 152 * https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html) 153 * It minimizes overhead that may otherwise occur when using so 154 * many volatile-mode reads. Using explicit acquireFences is 155 * logistically easier than targeting particular fields to be read 156 * in acquire mode: fences are just hoisted up as far as possible, 157 * to the entry points or loop headers of a few methods. A 158 * potential disadvantage is that these few remaining fences are 159 * not easily optimized away by compilers under exclusively 160 * single-thread use. It requires some care to avoid volatile 161 * mode reads of other fields. (Note that the memory semantics of 162 * a reference dependently read in plain mode exactly once are 163 * equivalent to those for atomic opaque mode.) Iterators and 164 * other traversals encounter each node and value exactly once. 165 * Other operations locate an element (or position to insert an 166 * element) via a sequence of dereferences. This search is broken 167 * into two parts. Method findPredecessor (and its specialized 168 * embeddings) searches index nodes only, returning a base-level 169 * predecessor of the key. Callers carry out the base-level 170 * search, restarting if encountering a marker preventing link 171 * modification. In some cases, it is possible to encounter a 172 * node multiple times while descending levels. For mutative 173 * operations, the reported value is validated using CAS (else 174 * retrying), preserving linearizability with respect to each 175 * other. Others may return any (non-null) value holding in the 176 * course of the method call. (Search-based methods also include 177 * some useless-looking explicit null checks designed to allow 178 * more fields to be nulled out upon removal, to reduce floating 179 * garbage, but which is not currently done, pending discovery of 180 * a way to do this with less impact on other operations.) 181 * 182 * To produce random values without interference across threads, 183 * we use within-JDK thread local random support (via the 184 * "secondary seed", to avoid interference with user-level 185 * ThreadLocalRandom.) 186 * 187 * For explanation of algorithms sharing at least a couple of 188 * features with this one, see Mikhail Fomitchev's thesis 189 * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis 190 * (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell's 191 * thesis (http://www.cs.chalmers.se/~phs/). 192 * 193 * Notation guide for local variables 194 * Node: b, n, f, p for predecessor, node, successor, aux 195 * Index: q, r, d for index node, right, down. 196 * Head: h 197 * Keys: k, key 198 * Values: v, value 199 * Comparisons: c 200 * 201 */ 202 203 import ( 204 "sync/atomic" 205 ) 206 207 var _ LinkedList[struct{}] = (*doublyLinkedList[struct{}])(nil) // Type check assertion 208 209 type nodeElementInListStatus uint8 210 211 const ( 212 notInList nodeElementInListStatus = iota 213 emtpyList 214 theOnlyOne 215 theFirstButNotTheLast 216 theLastButNotTheFirst 217 inMiddle 218 ) 219 220 // TODO Lock-free linked list 221 type doublyLinkedList[T comparable] struct { 222 root *NodeElement[T] 223 len atomic.Int64 224 } 225 226 func NewLinkedList[T comparable]() LinkedList[T] { 227 return new(doublyLinkedList[T]).init() 228 } 229 230 func (l *doublyLinkedList[T]) getRoot() *NodeElement[T] { 231 return l.root 232 } 233 234 func (l *doublyLinkedList[T]) getRootHead() *NodeElement[T] { 235 return l.root.next 236 } 237 238 func (l *doublyLinkedList[T]) setRootHead(targetE *NodeElement[T]) { 239 l.root.next = targetE 240 targetE.prev = l.root 241 } 242 243 func (l *doublyLinkedList[T]) getRootTail() *NodeElement[T] { 244 return l.root.prev 245 } 246 247 func (l *doublyLinkedList[T]) setRootTail(targetE *NodeElement[T]) { 248 l.root.prev = targetE 249 targetE.next = l.root 250 } 251 252 func (l *doublyLinkedList[T]) init() *doublyLinkedList[T] { 253 l.root = &NodeElement[T]{ 254 listRef: l, 255 } 256 l.setRootHead(l.root) 257 l.setRootTail(l.root) 258 l.len.Store(0) 259 return l 260 } 261 262 func (l *doublyLinkedList[T]) Len() int64 { 263 return l.len.Load() 264 } 265 266 func (l *doublyLinkedList[T]) checkElement(targetE *NodeElement[T]) (*NodeElement[T], nodeElementInListStatus) { 267 if l.len.Load() == 0 { 268 return l.getRoot(), emtpyList 269 } 270 271 if targetE == nil || targetE.listRef != l || targetE.prev == nil || targetE.next == nil { 272 return nil, notInList 273 } 274 275 // mem address compare 276 switch { 277 case targetE.Prev() == l.getRoot() && targetE.Next() == l.getRoot(): 278 // targetE is the first one and the last one 279 if l.getRootHead() != targetE || l.getRootTail() != targetE { 280 return nil, notInList 281 } 282 return targetE, theOnlyOne 283 case targetE.Prev() == l.getRoot() && targetE.Next() != l.getRoot(): 284 // targetE is the first one but not the last one 285 if targetE.Next().Prev() != targetE { 286 return nil, notInList 287 } 288 // Ignore l.setRootTail (tail) 289 return targetE, theFirstButNotTheLast 290 case targetE.Prev() != l.getRoot() && targetE.Next() == l.getRoot(): 291 // targetE is the last one but not the first one 292 if targetE.Prev().Next() != targetE { 293 return nil, notInList 294 } 295 // Ignore l.setRootHead (head) 296 return targetE, theLastButNotTheFirst 297 case targetE.Prev() != l.getRoot() && targetE.Next() != l.getRoot(): 298 // targetE is neither the first one nor the last one 299 if targetE.Prev().Next() != targetE && targetE.Next().Prev() != targetE { 300 return nil, notInList 301 } 302 return targetE, inMiddle 303 } 304 return nil, notInList 305 } 306 307 func (l *doublyLinkedList[T]) append(e *NodeElement[T]) *NodeElement[T] { 308 e.listRef = l 309 e.next, e.prev = nil, nil 310 311 if l.len.Load() == 0 { 312 // empty list, new append element is the first one 313 l.setRootHead(e) 314 l.setRootTail(e) 315 l.len.Add(1) 316 return e 317 } 318 319 lastOne := l.getRootTail() 320 lastOne.next = e 321 e.prev, e.next = lastOne, nil 322 l.setRootTail(e) 323 324 l.len.Add(1) 325 return e 326 } 327 328 func (l *doublyLinkedList[T]) Append(elements ...*NodeElement[T]) []*NodeElement[T] { 329 if l.getRootTail() == nil { 330 return nil 331 } 332 333 for i := 0; i < len(elements); i++ { 334 if elements[i] == nil { 335 continue 336 } 337 e := elements[i] 338 if e.listRef != l { 339 continue 340 } 341 elements[i] = l.append(e) 342 } 343 return elements 344 } 345 346 func (l *doublyLinkedList[T]) AppendValue(values ...T) []*NodeElement[T] { 347 // FIXME How to decrease the memory allocation for each operation? 348 // sync.Pool reused the released objects? 349 if len(values) <= 0 { 350 return nil 351 } else if len(values) == 1 { 352 return l.Append(newNodeElement(values[0], l)) 353 } 354 355 newElements := make([]*NodeElement[T], 0, len(values)) 356 for _, v := range values { 357 newElements = append(newElements, newNodeElement(v, l)) 358 } 359 return l.Append(newElements...) 360 } 361 362 func (l *doublyLinkedList[T]) insertAfter(newE, at *NodeElement[T]) *NodeElement[T] { 363 newE.prev = at 364 365 if l.len.Load() == 0 { 366 l.setRootHead(newE) 367 l.setRootTail(newE) 368 } else { 369 newE.next = at.Next() 370 at.next = newE 371 if newE.Next() != nil { 372 newE.Next().prev = newE 373 } 374 } 375 l.len.Add(1) 376 return newE 377 } 378 379 func (l *doublyLinkedList[T]) InsertAfter(v T, dstE *NodeElement[T]) *NodeElement[T] { 380 at, status := l.checkElement(dstE) 381 if status == notInList { 382 return nil 383 } 384 385 newE := newNodeElement(v, l) 386 newE = l.insertAfter(newE, at) 387 switch status { 388 case theOnlyOne, theLastButNotTheFirst: 389 l.setRootTail(newE) 390 default: 391 392 } 393 return newE 394 } 395 396 func (l *doublyLinkedList[T]) insertBefore(newE, at *NodeElement[T]) *NodeElement[T] { 397 newE.next = at 398 if l.len.Load() == 0 { 399 l.setRootHead(newE) 400 l.setRootTail(newE) 401 } else { 402 newE.prev = at.prev 403 at.prev = newE 404 if newE.Prev() != nil { 405 newE.Prev().next = newE 406 } 407 } 408 l.len.Add(1) 409 return newE 410 } 411 412 func (l *doublyLinkedList[T]) InsertBefore(v T, dstE *NodeElement[T]) *NodeElement[T] { 413 at, status := l.checkElement(dstE) 414 if status == notInList { 415 return nil 416 } 417 newE := newNodeElement(v, l) 418 newE = l.insertBefore(newE, at) 419 switch status { 420 case theOnlyOne, theFirstButNotTheLast: 421 l.setRootHead(newE) 422 default: 423 424 } 425 return newE 426 } 427 428 func (l *doublyLinkedList[T]) Remove(targetE *NodeElement[T]) *NodeElement[T] { 429 if l == nil || l.root == nil || l.len.Load() == 0 { 430 return nil 431 } 432 433 var ( 434 at *NodeElement[T] 435 status nodeElementInListStatus 436 ) 437 438 // doubly linked list removes element is free to do iteration, but we have to check the value if equals. 439 // avoid memory leaks 440 switch at, status = l.checkElement(targetE); status { 441 case theOnlyOne: 442 l.setRootHead(l.getRoot()) 443 l.setRootTail(l.getRoot()) 444 case theFirstButNotTheLast: 445 l.setRootHead(at.Next()) 446 at.Next().prev = l.getRoot() 447 case theLastButNotTheFirst: 448 l.setRootTail(at.Prev()) 449 at.Prev().next = l.getRoot() 450 case inMiddle: 451 at.Prev().next = at.next 452 at.Next().prev = at.prev 453 default: 454 return nil 455 } 456 457 // avoid memory leaks 458 at.listRef = nil 459 at.next = nil 460 at.prev = nil 461 462 l.len.Add(-1) 463 return at 464 } 465 466 // Foreach, allows remove linked list elements while iterating. 467 func (l *doublyLinkedList[T]) Foreach(fn func(idx int64, e *NodeElement[T]) error) error { 468 if l == nil || l.root == nil || fn == nil || l.len.Load() == 0 || 469 l.getRoot() == l.getRootHead() && l.getRoot() == l.getRootTail() { 470 return nil 471 } 472 473 var ( 474 iterator = l.getRoot().Next() 475 idx int64 = 0 476 ) 477 // Avoid remove in an iteration, result in memory leak 478 for iterator != l.getRoot() { 479 n := iterator.Next() 480 if err := fn(idx, iterator); err != nil { 481 return err 482 } 483 iterator = n 484 idx++ 485 } 486 return nil 487 } 488 489 // ReverseForeach, allows remove linked list elements while iterating. 490 func (l *doublyLinkedList[T]) ReverseForeach(fn func(idx int64, e *NodeElement[T])) { 491 if l == nil || l.root == nil || fn == nil || l.len.Load() == 0 || 492 l.getRoot() == l.getRootHead() && l.getRoot() == l.getRootTail() { 493 return 494 } 495 496 var ( 497 iterator = l.getRoot().Prev() 498 idx int64 = 0 499 ) 500 // Avoid remove in an iteration, result in memory leak 501 for iterator != l.getRoot() { 502 p := iterator.Prev() 503 fn(idx, iterator) 504 iterator = p 505 idx++ 506 } 507 } 508 509 func (l *doublyLinkedList[T]) FindFirst(targetV T, compareFn ...func(e *NodeElement[T]) bool) (*NodeElement[T], bool) { 510 if l == nil || l.root == nil || l.len.Load() == 0 { 511 return nil, false 512 } 513 514 if len(compareFn) <= 0 { 515 compareFn = []func(e *NodeElement[T]) bool{ 516 func(e *NodeElement[T]) bool { 517 return e.Value == targetV 518 }, 519 } 520 } 521 522 iterator := l.getRoot() 523 for iterator.HasNext() { 524 if compareFn[0](iterator.Next()) { 525 return iterator.Next(), true 526 } 527 iterator = iterator.Next() 528 } 529 return nil, false 530 } 531 532 func (l *doublyLinkedList[T]) Front() *NodeElement[T] { 533 if l == nil || l.root == nil || l.len.Load() == 0 { 534 return nil 535 } 536 537 return l.root.Next() 538 } 539 540 func (l *doublyLinkedList[T]) Back() *NodeElement[T] { 541 if l == nil || l.root == nil || l.len.Load() == 0 { 542 return nil 543 } 544 545 return l.root.Prev() 546 } 547 548 func (l *doublyLinkedList[T]) PushFront(v T) *NodeElement[T] { 549 if l == nil || l.root == nil { 550 return nil 551 } 552 553 return l.InsertBefore(v, l.getRootHead()) 554 } 555 556 func (l *doublyLinkedList[T]) PushBack(v T) *NodeElement[T] { 557 if l == nil || l.root == nil { 558 return nil 559 } 560 561 return l.InsertAfter(v, l.getRootTail()) 562 } 563 564 func (l *doublyLinkedList[T]) move(src, dst *NodeElement[T]) bool { 565 if src == dst { 566 return false 567 } 568 // Ordinarily, it is move src next to dst 569 src.Prev().next = src.next 570 src.Next().prev = src.prev 571 572 src.prev = dst 573 src.next = dst.Next() 574 src.Next().prev = src 575 dst.next = src 576 return true 577 } 578 579 func (l *doublyLinkedList[T]) MoveToFront(targetE *NodeElement[T]) bool { 580 if l == nil || l.root == nil || l.len.Load() == 0 { 581 return false 582 } 583 584 src, status := l.checkElement(targetE) 585 switch status { 586 case notInList, theOnlyOne, theFirstButNotTheLast: 587 return false 588 default: 589 } 590 return l.move(src, l.root) 591 } 592 593 func (l *doublyLinkedList[T]) MoveToBack(targetE *NodeElement[T]) bool { 594 if l == nil || l.root == nil || l.len.Load() == 0 { 595 return false 596 } 597 598 src, status := l.checkElement(targetE) 599 switch status { 600 case notInList, theLastButNotTheFirst, theOnlyOne: 601 return false 602 default: 603 } 604 return l.move(src, l.getRootTail()) 605 } 606 607 // MoveBefore. 608 // Ordinarily, it is move srcE just prev to dstE. 609 func (l *doublyLinkedList[T]) MoveBefore(srcE, dstE *NodeElement[T]) bool { 610 if l == nil || l.root == nil || l.len.Load() == 0 || srcE == dstE { 611 return false 612 } 613 614 var ( 615 dst, src *NodeElement[T] 616 dstStatus, srcStatus nodeElementInListStatus 617 ) 618 switch dst, dstStatus = l.checkElement(dstE); dstStatus { 619 case notInList, emtpyList, theOnlyOne: 620 return false 621 default: 622 } 623 624 switch src, srcStatus = l.checkElement(srcE); srcStatus { 625 case notInList, emtpyList, theOnlyOne: 626 return false 627 default: 628 } 629 630 dstPrev := dst.Prev() 631 return l.move(src, dstPrev) 632 } 633 634 func (l *doublyLinkedList[T]) MoveAfter(srcE, dstE *NodeElement[T]) bool { 635 if l == nil || l.root == nil || l.len.Load() == 0 || srcE == dstE { 636 return false 637 } 638 639 var ( 640 dst, src *NodeElement[T] 641 dstStatus, srcStatus nodeElementInListStatus 642 ) 643 switch dst, dstStatus = l.checkElement(dstE); dstStatus { 644 case notInList, emtpyList, theOnlyOne: 645 return false 646 default: 647 } 648 649 switch src, srcStatus = l.checkElement(srcE); srcStatus { 650 case notInList, emtpyList, theOnlyOne: 651 return false 652 default: 653 } 654 655 return l.move(src, dst) 656 } 657 658 func (l *doublyLinkedList[T]) PushFrontList(src LinkedList[T]) { 659 if l == nil || l.root == nil || l.len.Load() == 0 { 660 return 661 } 662 663 if dl, ok := src.(*doublyLinkedList[T]); !ok || dl != nil && dl.getRoot() == l.getRoot() { 664 // avoid type mismatch and self copy 665 return 666 } 667 668 for i, e := src.Len(), src.Back(); i > 0; i-- { 669 prev := e.Prev() 670 // Clean the node element reference, not a required operation 671 e.listRef = l 672 e.prev = nil 673 e.next = nil 674 l.insertAfter(e, l.root) 675 e = prev 676 } 677 } 678 679 func (l *doublyLinkedList[T]) PushBackList(src LinkedList[T]) { 680 if l == nil || l.root == nil || l.len.Load() == 0 { 681 return 682 } 683 684 if dl, ok := src.(*doublyLinkedList[T]); !ok || dl != nil && dl.getRoot() == l.getRoot() { 685 // avoid type mismatch and self copy 686 return 687 } 688 for i, e := src.Len(), src.Front(); i > 0; i-- { 689 next := e.Next() 690 // Clean the node element reference, not a required operation 691 e.listRef = l 692 e.prev = nil 693 e.next = nil 694 l.Append(e) 695 e = next 696 } 697 }