|
13 | 13 | */ |
14 | 14 | package org.jctools.maps; |
15 | 15 |
|
| 16 | +import org.jctools.util.RangeUtil; |
| 17 | + |
16 | 18 | import java.io.IOException; |
17 | 19 | import java.io.Serializable; |
18 | 20 | import java.lang.reflect.Field; |
19 | 21 | import java.util.*; |
20 | 22 | import java.util.concurrent.ConcurrentMap; |
21 | 23 | import java.util.concurrent.atomic.AtomicLongFieldUpdater; |
22 | 24 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; |
23 | | -import org.jctools.util.RangeUtil; |
24 | 25 |
|
25 | 26 | import static org.jctools.util.UnsafeAccess.UNSAFE; |
26 | 27 |
|
@@ -711,72 +712,87 @@ private static final Object putIfMatch( final NonBlockingHashMap topmap, final O |
711 | 712 | idx = (idx+1)&(len-1); // Reprobe! |
712 | 713 | } // End of spinning till we get a Key slot |
713 | 714 |
|
714 | | - // --- |
715 | | - // Found the proper Key slot, now update the matching Value slot. We |
716 | | - // never put a null, so Value slots monotonically move from null to |
717 | | - // not-null (deleted Values use Tombstone). Thus if 'V' is null we |
718 | | - // fail this fast cutout and fall into the check for table-full. |
719 | | - if( putval == V ) return V; // Fast cutout for no-change |
720 | | - |
721 | | - // See if we want to move to a new table (to avoid high average re-probe |
722 | | - // counts). We only check on the initial set of a Value from null to |
723 | | - // not-null (i.e., once per key-insert). Of course we got a 'free' check |
724 | | - // of newkvs once per key-compare (not really free, but paid-for by the |
725 | | - // time we get here). |
726 | | - if( newkvs == null && // New table-copy already spotted? |
727 | | - // Once per fresh key-insert check the hard way |
728 | | - ((V == null && chm.tableFull(reprobe_cnt,len)) || |
729 | | - // Or we found a Prime, but the JMM allowed reordering such that we |
730 | | - // did not spot the new table (very rare race here: the writing |
731 | | - // thread did a CAS of _newkvs then a store of a Prime. This thread |
732 | | - // reads the Prime, then reads _newkvs - but the read of Prime was so |
733 | | - // delayed (or the read of _newkvs was so accelerated) that they |
734 | | - // swapped and we still read a null _newkvs. The resize call below |
735 | | - // will do a CAS on _newkvs forcing the read. |
736 | | - V instanceof Prime) ) |
737 | | - newkvs = chm.resize(topmap,kvs); // Force the new table copy to start |
738 | | - // See if we are moving to a new table. |
739 | | - // If so, copy our slot and retry in the new table. |
740 | | - if( newkvs != null ) |
741 | | - return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal); |
| 715 | + while ( true ) { // Spin till we insert a value |
| 716 | + // --- |
| 717 | + // Found the proper Key slot, now update the matching Value slot. We |
| 718 | + // never put a null, so Value slots monotonically move from null to |
| 719 | + // not-null (deleted Values use Tombstone). Thus if 'V' is null we |
| 720 | + // fail this fast cutout and fall into the check for table-full. |
| 721 | + if( putval == V ) return V; // Fast cutout for no-change |
| 722 | + |
| 723 | + // See if we want to move to a new table (to avoid high average re-probe |
| 724 | + // counts). We only check on the initial set of a Value from null to |
| 725 | + // not-null (i.e., once per key-insert). Of course we got a 'free' check |
| 726 | + // of newkvs once per key-compare (not really free, but paid-for by the |
| 727 | + // time we get here). |
| 728 | + if( newkvs == null && // New table-copy already spotted? |
| 729 | + // Once per fresh key-insert check the hard way |
| 730 | + ((V == null && chm.tableFull(reprobe_cnt,len)) || |
| 731 | + // Or we found a Prime, but the JMM allowed reordering such that we |
| 732 | + // did not spot the new table (very rare race here: the writing |
| 733 | + // thread did a CAS of _newkvs then a store of a Prime. This thread |
| 734 | + // reads the Prime, then reads _newkvs - but the read of Prime was so |
| 735 | + // delayed (or the read of _newkvs was so accelerated) that they |
| 736 | + // swapped and we still read a null _newkvs. The resize call below |
| 737 | + // will do a CAS on _newkvs forcing the read. |
| 738 | + V instanceof Prime) ) |
| 739 | + newkvs = chm.resize(topmap,kvs); // Force the new table copy to start |
| 740 | + // See if we are moving to a new table. |
| 741 | + // If so, copy our slot and retry in the new table. |
| 742 | + if( newkvs != null ) |
| 743 | + return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal); |
742 | 744 |
|
743 | | - // --- |
744 | | - // We are finally prepared to update the existing table |
745 | | - assert !(V instanceof Prime); |
746 | | - |
747 | | - // Must match old, and we do not? Then bail out now. Note that either V |
748 | | - // or expVal might be TOMBSTONE. Also V can be null, if we've never |
749 | | - // inserted a value before. expVal can be null if we are called from |
750 | | - // copy_slot. |
751 | | - |
752 | | - if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all? |
753 | | - V != expVal && // No instant match already? |
754 | | - (expVal != MATCH_ANY || V == TOMBSTONE || V == null) && |
755 | | - !(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo |
756 | | - (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last |
757 | | - return V; // Do not update! |
758 | | - |
759 | | - // Actually change the Value in the Key,Value pair |
760 | | - if( CAS_val(kvs, idx, V, putval ) ) { |
761 | | - // CAS succeeded - we did the update! |
762 | | - // Both normal put's and table-copy calls putIfMatch, but table-copy |
763 | | - // does not (effectively) increase the number of live k/v pairs. |
764 | | - if( expVal != null ) { |
765 | | - // Adjust sizes - a striped counter |
766 | | - if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) chm._size.add( 1); |
767 | | - if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) chm._size.add(-1); |
768 | | - } |
769 | | - } else { // Else CAS failed |
| 745 | + // --- |
| 746 | + // We are finally prepared to update the existing table |
| 747 | + assert !(V instanceof Prime); |
| 748 | + |
| 749 | + // Must match old, and we do not? Then bail out now. Note that either V |
| 750 | + // or expVal might be TOMBSTONE. Also V can be null, if we've never |
| 751 | + // inserted a value before. expVal can be null if we are called from |
| 752 | + // copy_slot. |
| 753 | + if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all? |
| 754 | + V != expVal && // No instant match already? |
| 755 | + (expVal != MATCH_ANY || V == TOMBSTONE || V == null) && |
| 756 | + !(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo |
| 757 | + (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last |
| 758 | + return V; // Do not update! |
| 759 | + |
| 760 | + // Actually change the Value in the Key,Value pair |
| 761 | + if( CAS_val(kvs, idx, V, putval ) ) break; |
| 762 | + |
| 763 | + // CAS failed |
| 764 | + // Because we have no witness, we do not know why it failed. |
| 765 | + // Indeed, by the time we look again the value under test might have flipped |
| 766 | + // a thousand times and now be the expected value (despite the CAS failing). |
| 767 | + // Check for the never-succeed condition of a Prime value and jump to any |
| 768 | + // nested table, or else just re-run. |
| 769 | + |
| 770 | + // We would not need this load at all if CAS returned the value on which |
| 771 | + // the CAS failed (AKA witness). The new CAS semantics are supported via |
| 772 | + // VarHandle in JDK9. |
770 | 773 | V = val(kvs,idx); // Get new value |
| 774 | + |
771 | 775 | // If a Prime'd value got installed, we need to re-run the put on the |
772 | 776 | // new table. Otherwise we lost the CAS to another racing put. |
773 | | - // Simply retry from the start. |
774 | 777 | if( V instanceof Prime ) |
775 | 778 | return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal); |
| 779 | + |
| 780 | + // Simply retry from the start. |
| 781 | + // NOTE: need the fence, since otherwise 'val(kvs,idx)' load could be hoisted |
| 782 | + // out of loop. |
| 783 | + int dummy = DUMMY_VOLATILE; |
| 784 | + } |
| 785 | + |
| 786 | + // CAS succeeded - we did the update! |
| 787 | + // Both normal put's and table-copy calls putIfMatch, but table-copy |
| 788 | + // does not (effectively) increase the number of live k/v pairs. |
| 789 | + if( expVal != null ) { |
| 790 | + // Adjust sizes - a striped counter |
| 791 | + if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) chm._size.add( 1); |
| 792 | + if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) chm._size.add(-1); |
776 | 793 | } |
777 | | - // Win or lose the CAS, we are done. If we won then we know the update |
778 | | - // happened as expected. If we lost, it means "we won but another thread |
779 | | - // immediately stomped our update with no chance of a reader reading". |
| 794 | + |
| 795 | + // We won; we know the update happened as expected. |
780 | 796 | return (V==null && expVal!=null) ? TOMBSTONE : V; |
781 | 797 | } |
782 | 798 |
|
|
0 commit comments