// A Java Program to more formally test the ideas in my Non-Blocking-Hash-Map
import java.util.*;
import java.util.concurrent.*;
class NBHM_Tester {
// Set of States for an individual State Machine.
// Each State is really a pair of memory words.
// The first word is only 0, K, X; the 2nd word is only 0, A/a, B/b, _ or x.
enum S { // States
BAD (0),
MT (1), // No Key, No Value
X0 (2), // Key is X'd out (slot is dead, nothing to copy)
K0 (3), // Key only, Value is NULL
KA (4), // Key/Value-A pair
Ka (5), // Key/Value-A' pair
KB (6), // Key/Value-B pair
Kb (7), // Key/Value-B' pair
K_ (8), // Key/Tombstone - deleted
KX (9); // Key/X pair - copied
// A field to let me cheapo map to integers
final int _idx;
S(int idx) { _idx=idx; }
static final int MAX = values().length;
// --- compute_reached ---------------------------------------------------
// Used to test sanity of the allowed-transitions
private void compute_reached(boolean [] reached) {
if( reached[_idx] ) return; // Already reached this state
reached[_idx] = true; // First time reached this state
S[] T = _allowed_transitions; // Short handy name
// Visit all transitions...
for( int i=0; i<T.length; i+= 2 )
if( T[i] == this ) // If see a transition starting from this state
T[i+1].compute_reached(reached); // compute reaching from here
}
public static S [] _prime = {
BAD,BAD,BAD,BAD,
Ka,KA,Kb,KB,
BAD,BAD
};
public S prime() { return _prime[_idx]; }
};
// List of allowed-transitions as S-pairs
public static final S[] _allowed_transitions = {
S.BAD, S.MT, // Bogus starting transition
S.MT, S.X0, // Empty -> dead_slot
S.MT, S.K0, // Empty -> Key insertion
S.K0, S.KA, // Key -> Key/A pair
S.K0, S.Ka, // Key -> Key/A' pair
S.K0, S.KB, // Key -> Key/B pair
S.K0, S.Kb, // Key -> Key/B' pair
S.K0, S.K_, // Key -> deleted
S.KA, S.KB, // Key/A -> Key/B
S.KA, S.K_, // Key/A -> deleted
S.KB, S.KA, // Key/B -> Key/A
S.KB, S.K_, // Key/B -> deleted
S.K_, S.KA, // deleted -> Key/A
S.K_, S.KB, // deleted -> Key/B
S.Ka, S.KA, // Key/A' -> Key/A (strip prime)
S.Ka, S.Kb, // Key/A' -> Key/B'
S.Ka, S.K0, // Key/A' -> Key alone (same as deleted-prime)
S.Ka, S.KB, // Key/A' -> Key/B (last write overrides copy)
S.Ka, S.K_, // Key/A' -> Key delete
S.Kb, S.KB, // Key/B' -> Key/B (strip prime)
S.Kb, S.Ka, // Key/B' -> Key/A'
S.Kb, S.K0, // Key/B' -> Key alone (same as deleted-prime)
S.Kb, S.KA, // Key/B' -> Key/A (last write overrides copy)
S.Kb, S.K_, // Key/B' -> Key delete
S.K0, S.KX, // Key -> copied
S.KA, S.KX, // Key/A -> copied
S.KB, S.KX, // Key/B -> copied
S.K_, S.KX, // deleted -> copied
null
};
// power-of-2 larger than _allowed_transitions.length
private static final int LOG2TRAN = 6;
private static final int MAXTRAN = 1<<LOG2TRAN;
private static final int[][] fill_state_machine() {
int [][] sm = new int[S.MAX][S.MAX];
S[] T = _allowed_transitions; // Short handy name
// Visit all transitions...
for( int i=2; i<T.length-1; i+= 2 )
sm[T[i+0]._idx][T[i+1]._idx] = i;
return sm;
}
// Array of allowed transitions
public static final int[][] _state_machine = fill_state_machine();
// Is this transition allowed as part of the state-machine?
public static final int is_SM(S x,S y) { return _state_machine[x._idx][y._idx]; }
// --- Thrd ----------------------------------------------------------------
// Notion of an action performed by a single thread, such as 'put(K,A)' or
// 'delete(K)' - always with respect to key K. This action will turn into a
// series of state-machine transitions (or perhaps a request to move to a
// newer state machine) or
public static abstract class Thrd {
final String _name; // Nice thread name
final int _tid; // This thread index; invariant: _Thrds[_tid]==this
final boolean _ordered[]; // This thread is ordered after what other threads?
static int _tids; // Max number of threads
static final Thrd[] _thrds = new Thrd[10]; // Array of them
// Thread that can begin at any time
Thrd( String name ) {
_tid = _tids++;
_name = name;
_thrds[_tid] = this;
_ordered = null; // shortcut for un-ordered
}
// Thread that must wait until thread t0 has seen 'at_goal'
Thrd( String name, Thrd t0 ) {
_tid = _tids++;
_name = name;
_thrds[_tid] = this;
_ordered = new boolean[_thrds.length];
_ordered[t0._tid] = true;
}
// Thread that must wait until threads t0 and t1 have seen 'at_goal'
Thrd( String name, Thrd t0, Thrd t1 ) {
_tid = _tids++;
_name = name;
_thrds[_tid] = this;
_ordered = new boolean[_thrds.length];
_ordered[t0._tid] = true;
_ordered[t1._tid] = true;
}
//abstract History step(History h);
//abstract boolean at_goal(History h);
public String toString() { return _name; }
// threads cannot start until prior-ordered-threads finish.
// passed in an array of active Thrds (or NULL)
public boolean can_start( Thrd[] thrds ) {
if( _ordered == null ) return true;
for( int i=0; i<thrds.length; i++ )
if( thrds[i] != null && _ordered[thrds[i]._tid] )
return false;
return true;
}
abstract boolean at_goal(History h);
abstract History step( History h );
}
// --- Thrd_A --------------------------------------------------------------
// A Thrd class to do a 'put(A)'
public static class Thrd_A extends Thrd {
Thrd_A( String n ) { super(n); }
Thrd_A( String n, Thrd t0 ) { super(n,t0); }
Thrd_A( String n, Thrd t0, Thrd t1 ) { super(n,t0,t1); }
History step( History h ) {
return step_impl(h,false,h._old);
}
History step_impl( History h, boolean old_or_new, S x ) {
int tran = 0;
switch( x ) {
case MT: tran = is_SM(x,S.K0); break;
case K0:
case Ka:
case KB:
case Kb:
case K_: tran = is_SM(x,S.KA); break;
case X0:
case KX: return step_impl(h,true,h._new); // try again in new table
default:
assert !at_goal(h) : "why you asking for a step when at_goal?";
throw new Error("Unimplemented "+x);
}
assert tran != 0 : "broken step function from "+x;
return h.make(new Event( x, old_or_new, tran ),this);
}
boolean at_goal(History h) {
return
(h._old == S.KA) || // Old is KA OR
(h._new == S.KA && // And new is KA
h.old_is_dead()); // Signaled to new table
}
}
// --- Thrd_B --------------------------------------------------------------
// A Thrd class to do a 'put(B)'
public static class Thrd_B extends Thrd {
Thrd_B( String n ) { super(n); }
Thrd_B( String n, Thrd t0 ) { super(n,t0); }
Thrd_B( String n, Thrd t0, Thrd t1 ) { super(n,t0,t1); }
History step( History h ) {
return step_impl(h,false,h._old);
}
History step_impl( History h, boolean old_or_new, S x ) {
int tran = 0;
switch( x ) {
case MT: tran = is_SM(x,S.K0); break;
case K0:
case KA:
case Ka:
case Kb:
case K_: tran = is_SM(x,S.KB); break;
case X0:
case KX: return step_impl(h,true,h._new); // try again in new table
default:
assert !at_goal(h) : "why you asking for a step when at_goal?";
throw new Error("Unimplemented "+x);
}
assert tran != 0 : "broken step function from "+x;
return h.make(new Event( x, old_or_new, tran ),this);
}
boolean at_goal(History h) {
return
(h._old == S.KB) || // Old is KB OR
(h._new == S.KB && // And new is KB
h.old_is_dead()); // Signaled to new table
}
}
// --- Thrd_del ------------------------------------------------------------
// A Thrd class to do a 'delete()'
public static class Thrd_del extends Thrd {
Thrd_del( String n ) { super(n); }
Thrd_del( String n, Thrd t0 ) { super(n,t0); }
Thrd_del( String n, Thrd t0, Thrd t1 ) { super(n,t0,t1); }
History step( History h ) {
return step_impl(h,false,h._old);
}
History step_impl( History h, boolean old_or_new, S x ) {
switch( x ) {
case KA:
case Ka:
case KB:
case Kb: {
int tran = is_SM(x,S.K_);
if( tran == 0 ) System.out.println(" del="+tran+" now="+x);
return h.make(new Event( x, old_or_new, is_SM(x,S.K_) ),this);
}
case X0:
case KX: return step_impl(h,true,h._new); // try again in new table
default:
assert !at_goal(h) : "why you asking for a step when at_goal?";
throw new Error("Unimplemented "+x);
}
}
boolean at_goal(History h) {
return
( h._old == S.MT || h._old == S.K0 || h._old == S.K_) || // Old is K0 or K_ OR
((h._new == S.MT || h._new == S.K0 || h._new == S.K_) && // new is K0 or K_ and
h.old_is_dead()); // Signaled to new table
}
}
// --- Thrd_copy -----------------------------------------------------------
// A Thrd class to do copy from the old table to the new table
public static class Thrd_copy extends Thrd {
Thrd_copy( String n ) { super(n); }
Thrd_copy( String n, Thrd t0 ) { super(n,t0); }
Thrd_copy( String n, Thrd t0, Thrd t1 ) { super(n,t0,t1); }
History step_impl( History h, boolean old_or_new, S sold, S snew ) {
if( (old_or_new ? h._new : h._old) != sold )
// CAS fails because memory has changed; no actual transition happens;
// re-read memory & try again
return h.append_copy_reader(this,old_or_new);
return h.make(new Event(sold,old_or_new,is_SM(sold,snew)),this);
}
History step( History h ) {
S c_old = h.last_read( this, false ); // Last read value from OLD table
S c_new = h.last_read( this, true ); // Last read value from NEW table
switch( c_old ) {
case MT: return step_impl(h,false,c_old,S.X0);
case K0: return step_impl(h,false,c_old,S.KX);
case KA:
switch( c_new ) {
case MT: return step_impl(h,true ,c_new,S.K0);
case K0: return step_impl(h,true ,c_new,S.Ka);
case KA:
case KB:
case Ka: return step_impl(h,false,c_old,S.KX);
case Kb: return step_impl(h,true ,c_new,S.Ka);
}
break;
case KB:
switch( c_new ) {
case MT: return step_impl(h,true ,c_new,S.K0);
case K0: return step_impl(h,true ,c_new,S.Kb);
case Ka: return step_impl(h,true ,c_new,S.Kb);
case KA:
case KB:
case Kb: return step_impl(h,false,c_old,S.KX);
}
break;
case X0:
case KX:
switch( c_new ) {
case Ka: return step_impl(h,true ,c_new,S.KA);
case Kb: return step_impl(h,true ,c_new,S.KB);
case K0: return step_impl(h,true ,c_new,S.K_);
}
break;
case K_:
switch( c_new ) {
case Ka:
case Kb: return step_impl(h,true ,c_new,S.K0);
case MT:
case K0: return step_impl(h,false,c_old,S.KX);
}
break;
}
throw new Error("Unimplemented copy "+h+" for old "+c_old + " for new "+c_new);
}
boolean at_goal(History h) {
S hold = h.last_read( this, false ); // Last read value from OLD table
S hnew = h.last_read( this, true ); // Last read value from NEW table
return (hold==S.X0 || hold==S.KX) &&
hnew != S.Ka && hnew != S.Kb && hnew != S.K0;
}
}
// --- Event ---------------------------------------------------------------
// An Event is a transition in either the old or new finite state machine,
// or a coherent read by some copy thread
static public final class Event {
// This is a bit-field, the low bits hold the transitions, the high
// bits hold the TIDs of copy-threads.
private final int _tran;
public final int hashCode() { return _tran; }
public final boolean equals( Object x ) {
if( !(x instanceof Event) ) return false;
return ((Event)x)._tran == _tran; // Bits-compare-equal
}
public final int tran() { return _tran & (MAXTRAN-1); }
public Event( S begin, boolean old_or_new, int tran ) {
// Sane transitions
assert tran != 0 : "reserved for copy-read Events";
assert (tran&1)==0 && _allowed_transitions[tran] == begin;
if( old_or_new ) tran |= (1<<LOG2TRAN); // Mark as a new-transition
_tran = tran;
assert !is_copyread();
}
public boolean old_or_new() { return (_tran & (1<<LOG2TRAN)) != 0; }
public boolean is_copyread( ) { return tran() == 0; }
public Event( Thrd_copy copy, boolean old_or_new ) {
int tran = 0;
if( old_or_new ) tran |= (1<<LOG2TRAN); // Mark as a new-transition
_tran = tran;
assert is_copyread();
}
}
// --- History--------------------------------------------------------------
// A sequence of interesting Events: finite-state-machine transitions
// (including which thread caused them) for both old and new FSM's and
// coherent reads by COPY threads.
// The event arrays are immutable and hashed for equivalence, and are used
// to explore the state-sequence-space. The sequence always starts from the
// same starting point: both FSM's at state MT. Two histories are the same
// if the FSM transitions & reads occur in the same order (reguardless of
// thread).
// All threads that can could have done a particular transition are recorded
// for that Event, so that later I can piece together plausible thread
// interleavings that lead to bad states.
static public class History {
final Event [] _events; // What event
final int [] _tids; // Which threads got here
final S _old; // Cache of last old-FSM state
final S _new; // Cache of last new-FSM state
boolean _complete; // Set to true if there exists a path where all threads are done
// helper: old FSM is dead
boolean old_is_dead() { return _old == S.X0 || _old == S.KX; }
// --- hash --------------------------------------------------------------
private final static ConcurrentHashMap<History,History> hash =
new ConcurrentHashMap<History,History>();
private int _hash;
public final int hashCode() { return _hash; }
// Two Historys are 'equals' if they have the same state sequences.
public final boolean equals( Object x ) {
if( !(x instanceof History) ) return false;
History h = (History)x;
if( _events.length != h._events.length ) return false;
for( int i=0; i<_events.length; i++ )
if( !_events[i].equals(h._events[i]) )
return false;
return true;
}
// --- canonical -----------------------------------------------------------
// Return the canonical History here, using the hash table.
// Allows Histories to be compared using pointer-equivalence.
private History canonical( ) {
if( _events.length > 0 ) {
Event e = _events[_events.length-1];
S end = _allowed_transitions[e.tran()+1];
assert e.old_or_new() || (end != S.Ka && end != S.Kb) :
"No Primes in old table: "+e.old_or_new()+" "+end;
}
History old = hash.putIfAbsent(this,this);
if( old == null ) return this;
// Combine thread-ids in the old History
for( int i=0; i<_tids.length; i++ )
old._tids[i] |= _tids[i];
return old;
}
// --- History -----------------------------------------------------------
// The initial empty history
private History() {
_events = new Event[0];
_tids = new int[_events.length];
_hash = 1;
_old = S.MT;
_new = S.MT;
}
public static History make() { return new History().canonical(); }
// --- History -----------------------------------------------------------
// Extend an existing history
private History(History h, Event e, Thrd t) {
assert e.is_copyread() || (e.old_or_new() ? h._new : h._old) == _allowed_transitions[e.tran()];
int idx = h._events.length;
//assert (idx == 0) || !e.is_copyread() || !h._events[idx-1].is_copyread() : "no 2 copyreads in a row "+h;
_events = new Event[idx+1];
System.arraycopy(h._events,0,_events,0,idx);
_tids = new int [idx+1];
System.arraycopy(h._tids ,0,_tids ,0,idx);
_events[idx] = e;
_tids [idx] = t != null ? (1<<t._tid) : 0;
_hash = h.hashCode() + e.hashCode();
if( e.is_copyread() ) {
_old = h._old; _new = h._new;
} else {
if( e.old_or_new() ) { _new = _allowed_transitions[e.tran()+1]; _old=h._old; }
else { _old = _allowed_transitions[e.tran()+1]; _new=h._new; }
}
}
public History make(Event e, Thrd t) {
return new History(this,e,t).canonical();
}
// Does this history already exist?
public History check(Event e, Thrd t) { return hash.get(new History(this,e,t)); }
public History add_at_goal( Thrd t ) {
if( _tids.length > 0 ) _tids[_tids.length-1] |= (1<<t._tid);
return this;
}
public History append_copy_reader( Thrd_copy t, boolean old_or_new ) {
Event e = _events[_events.length-1];
if( e.is_copyread() && // Allow more than 1 thread to read at same time
e.old_or_new() == old_or_new &&
_tids.length > 0 )
return add_at_goal(t); // BREAKS COPYREAD HASHING?
Event ec = new Event( t, old_or_new ); // Add a coherent-copy-read
return new History(this,ec,t).canonical();
}
// --- last_read ---------------------------------------------------------
// Last value read by this thread for the given FSM. Only interesting for
// making changes in the OTHER FSM.
public S last_read( Thrd_copy copy, boolean old_or_new ) {
for( int i=_events.length-1; i>=0; i-- ) {
Event e = _events[i];
if( e.old_or_new() == old_or_new ) { // Matching FSM
boolean was = (_tids[i]&(1<<copy._tid)) != 0;
if( e.is_copyread( ) && // We just did a copy-thread-read?
was ) { // of the correct thread?
for( i=i-1; i>=0 ; i-- ) { // Find last update to this FSM
e = _events[i];
if( e.old_or_new() == old_or_new && !e.is_copyread() ) {
return _allowed_transitions[e.tran()+1];
}
}
return S.MT;
}
// Or a normal copy-thread update
if( was )
return _allowed_transitions[e.tran()+1];
}
}
return S.MT; // Not ever read before
}
// --- toString ----------------------------------------------------------
// Pretty print
public String toString() {
S s_old = S.MT;
S s_new = S.MT;
StringBuffer buf = new StringBuffer();
buf.append("(").append(s_old).append("/").append(s_new);
for( int i=0; i<_events.length; i++ ) {
buf.append(" --");
// Print all threads involved here
long tids = _tids[i];
int t=0;
boolean first = true;
while( tids != 0 ) {
if( (tids & (1<<t)) != 0 ) {
tids -= (1<<t);
if( !first ) buf.append(",");
buf.append(Thrd._thrds[t]);
first = false;
}
t++;
}
// Update the states based on the transition
Event e = _events[i];
S s = e.old_or_new() ? s_new : s_old;
if( e.is_copyread() ) {
buf.append("--> [").append(e.old_or_new()?"new ":"old ").append(s).append("]");
} else {
assert _allowed_transitions[e.tran()] == s;
s = _allowed_transitions[e.tran()+1]; // New State
if( e.old_or_new() ) s_new = s; else s_old = s;
// Print the New World Order
buf.append("--> ").append(s_old).append("/").append(s_new);
}
}
buf.append(")");
assert s_old == _old;
assert s_new == _new;
return buf.toString();
}
// --- printAll ----------------------------------------------------------
// Pretty print ALL histories
public static void printAll () {
for( History h : hash.keySet() ) {
System.out.println(h);
}
}
public static void printComplete() {
for( History h : hash.keySet() ) {
if( h._complete )
System.out.println(h);
}
}
// --- witness -----------------------------------------------------------
// Report back all the visible 'get' values possible
public static void printWitness() {
for( History h : hash.keySet() ) {
if( h._complete )
System.out.println(h.witness()+" "+h);
}
}
/**
* Describe <code>witness</code> method here.
*
* @return a <code>String</code> value
*/
public String witness() {
S s_old = S.MT;
S s_new = S.MT;
S s_last = S.MT;
StringBuffer buf = new StringBuffer();
buf.append("{");
for( int i=0; i<_events.length; i++ ) {
Event e = _events[i];
if( !e.is_copyread() ) {
// Update the states based on the transition
S s = e.old_or_new() ? s_new : s_old;
s = _allowed_transitions[e.tran()+1]; // New State
if( e.old_or_new() ) s_new = s; else s_old = s;
// Read from old first, or new if old is dead
s = (s_old == S.X0 || s_old == S.KX) ? s_new : s_old;
// 'flatten' answers
if( s == S.K0 || s == S.K_ ) s = S.MT;
if( s == S.Ka ) s = S.KA;
if( s == S.Kb ) s = S.KB;
if( s != s_last ) {
buf.append(s).append(" ");
s_last = s;
}
}
}
buf.append("}");
return buf.toString();
}
// --- search ------------------------------------------------------------
// Search the state space for running N threads, each stepping to some
// goal. Try stepping each thread 1 step from the current state.
public void search(final Thrd[] q) {
// Search 1 step for each thread
boolean all_threads_done = true;
for( int i=0; i<q.length; i++ ) {
Thrd T = q[i];
if( T == null ) // Can we advance this thread anymore?
continue; // Nope
all_threads_done = false;
if( !T.can_start(q) ) // Can start this thread yet?
continue; // Nope
// Do One Step, unless at-goal already
History h = T.at_goal(this) ? this.add_at_goal(T) : T.step(this);
// Found a new state; must explore
Thrd[] r = q.clone(); // Clone old thread list
if( T.at_goal(h) ) // Hit goal?
r[i] = null; // No more advance on this thread
h.search(r); // Search On!
}
if( all_threads_done ) // All threads done at this history
_complete = true; // so mark it
}
}
// --- main ----------------------------------------------------------------
public static void main( String[] args ) {
if( MAXTRAN <= _allowed_transitions.length )
throw new Error("Write some log2 function for MAXTRAN or bump its size");
// Validate that all states reachable
boolean [] reached = new boolean[S.MAX];
S.MT.compute_reached(reached);
for( S s : S.values() )
if( !reached[s._idx] && s != S.BAD )
throw new Error("State "+s+" not reachable from any transition");
Thrd a0 = new Thrd_A("a0");
//Thrd a1 = new Thrd_A("a1");
Thrd b0 = new Thrd_B("b0");
//Thrd d0 = new Thrd_del("d0");
//Thrd d1 = new Thrd_del("d1");
Thrd c0 = new Thrd_copy("c0");
Thrd c1 = new Thrd_copy("c1");
Thrd[] thrds = {a0,b0,c0,c1};
History h = History.make();
h.search(thrds);
// need to check for no-dropped-last-write and also no-flip-flops
// need to remove extra paths with redundant copy-reads, such as when c0
// does a read, another thread updates, c0 fails a CAS and does another
// read.
//History.printAll();
//History.printComplete();
History.printWitness();
}
}