/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.map;
import net.openhft.chronicle.algo.locks.AcquisitionStrategies;
import net.openhft.chronicle.algo.locks.ReadWriteLockingStrategy;
import net.openhft.chronicle.algo.locks.TryAcquireOperations;
import net.openhft.chronicle.algo.locks.VanillaReadWriteWithWaitsLockingStrategy;
import net.openhft.chronicle.bytes.Byteable;
import net.openhft.chronicle.map.utility.ProcessInstanceLimiter;
import net.openhft.chronicle.values.Array;
import net.openhft.chronicle.values.Group;
import net.openhft.chronicle.values.Values;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import static net.openhft.chronicle.algo.bytes.Access.checkedBytesStoreAccess;
import static org.junit.Assert.assertEquals;
/**
* @author jack shirazi
*/
public class CHMTest5 {
public static final String TEST_KEY = "whatever";
public static int NUMBER_OF_PROCESSES_ALLOWED = 2;
public static void main(String[] args) throws IOException {
//First create (or access if already created) the shared map
ChronicleMapBuilder<String, CHMTest5Data> builder =
ChronicleMapBuilder.of(String.class, CHMTest5Data.class)
.entries(1000);
//// don't include this, just to check it is as expected.
assertEquals(8, builder.minSegments());
//// end of test
String chmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "CHMTest5";
ChronicleMap<String, CHMTest5Data> theSharedMap =
builder.createPersistedTo(new File(chmPath));
//Now get the shared data object, auto-creating if it's not there
CHMTest5Data data = Values.newNativeReference(CHMTest5Data.class);
theSharedMap.acquireUsing(TEST_KEY, data);
//if this was newly created, we need to set the max allowed
//Note, the object is pointing at shared memory and writes
//directly to it, so no need to put() it back into the map
if (data.getMaxNumberOfProcessesAllowed() != NUMBER_OF_PROCESSES_ALLOWED) {
//it's either a new object, set to 0, or
//another process set it to an invalid value
if (data.compareAndSwapMaxNumberOfProcessesAllowed(0, NUMBER_OF_PROCESSES_ALLOWED)) {
//What we expected, everything's good
} else {
//something else set a value, if it's not 2 we've got a conflict
if (data.getMaxNumberOfProcessesAllowed() != NUMBER_OF_PROCESSES_ALLOWED) {
System.out.println("Incorrect configuration found, expected " +
NUMBER_OF_PROCESSES_ALLOWED + " slots, instead found " +
data.getMaxNumberOfProcessesAllowed() + " - exiting");
System.exit(0);
}
}
}
//Now, we look for an empty slot in the "time" array.
//Because this initial implementation of the data object
//is directly accessing the shared memory, we just operate
//on it as a shared object
// There would be no need to lock the MaxNumberOfProcessesAllowed field
// as we used CAS to update and it's read-only after that
// but we need to lock access to the Time array
long[] times1 = new long[NUMBER_OF_PROCESSES_ALLOWED];
boolean locked = false;
for (int i = 0; i < 1000000; i++) {
//try up to 1 second
if (data.tryLockNanosEntry(1000L)) {
locked = true;
break;
}
}
if (!locked) {
System.out.println("Unable to acquire a lock on the time array - exiting");
System.exit(0);
}
try {
//we've got the lock, now copy the array
for (int i = 0; i < times1.length; i++) {
times1[i] = data.getTimeAt(i);
}
} finally {
//and release the lock
try {
data.unlockEntry();
} catch (IllegalMonitorStateException e) {
//odd, but we'll be unlocked either way
System.out.println("Unexpected state: " + e);
e.printStackTrace();
}
}
pause(300L);
long[] times2 = new long[NUMBER_OF_PROCESSES_ALLOWED];
locked = false;
for (int i = 0; i < 1000000; i++) {
//try up to 1 second
if (data.tryLockNanosEntry(1000L)) {
locked = true;
break;
}
}
if (!locked) {
System.out.println("Unable to acquire a lock on the time array - exiting");
System.exit(0);
}
try {
//we've got the lock, now copy the array
for (int i = 0; i < times2.length; i++) {
times2[i] = data.getTimeAt(i);
}
} finally {
//and release the lock
try {
data.unlockEntry();
} catch (IllegalMonitorStateException e) {
//odd, but we'll be unlocked either way
System.out.println("Unexpected state: " + e);
e.printStackTrace();
}
}
//look for a slot that hasn't changed in that 300ms pause
int slotindex = 0;
long lastUpdateTime = -1;
for (; slotindex < times1.length; slotindex++) {
if (times2[slotindex] == times1[slotindex]) {
//we have an index which has not been updated by anything else
//in the 300ms pause, so we have a spare slot - we use this slot
long timenow = System.currentTimeMillis();
locked = false;
for (int i = 0; i < 1000000; i++) {
//try up to 1 second
if (data.tryLockNanosEntry(1000L)) {
locked = true;
break;
}
}
if (!locked) {
System.out.println("Unable to acquire a lock on the time array - exiting");
System.exit(0);
}
try {
data.setTimeAt(slotindex, timenow);
} finally {
//and release the lock
try {
data.unlockEntry();
} catch (IllegalMonitorStateException e) {
//odd, but we'll be unlocked either way
System.out.println("Unexpected state: " + e);
e.printStackTrace();
}
}
//Now we have successfully acquired a slot
lastUpdateTime = timenow;
System.out.println("We have started on slot " + slotindex);
break;
}
}
if (lastUpdateTime == -1) {
System.out.println("We failed to find a free slot, so terminating now");
System.exit(0);
}
//Now let's run for 60 seconds, updating the slot every 100ms
for (int count = 0; count < 600; count++) {
pause(100L);
long timenow = System.currentTimeMillis();
locked = false;
for (int i = 0; i < 1000000; i++) {
//try up to 1 second
if (data.tryLockNanosEntry(1000L)) {
locked = true;
break;
}
}
if (!locked) {
System.out.println("Unable to acquire a lock on the time array - exiting");
System.exit(0);
}
try {
if (lastUpdateTime == data.getTimeAt(slotindex)) {
//That's what we expect so just update the slot
data.setTimeAt(slotindex, timenow);
lastUpdateTime = timenow;
} else {
//Some other process has hijacked our slot - so
//the only thing we can do is terminate
System.out.println("Another process has hijacked our slot " + slotindex + ", so terminating now");
System.exit(0);
}
} finally {
//and release the lock
try {
data.unlockEntry();
} catch (IllegalMonitorStateException e) {
//odd, but we'll be unlocked either way
System.out.println("Unexpected state: " + e);
e.printStackTrace();
}
}
}
System.out.println("Exiting slot " + slotindex + " after completing the full test.");
}
public static void pause(long pause) {
ProcessInstanceLimiter.pause(pause);
}
public interface CHMTest5Data {
@Group(0)
long getEntryLockState();
void setEntryLockState(long entryLockState);
@Group(1)
int getMaxNumberOfProcessesAllowed();
void setMaxNumberOfProcessesAllowed(int max);
boolean compareAndSwapMaxNumberOfProcessesAllowed(int expected, int value);
@Group(1)
@Array(length = 4)
void setTimeAt(int index, long time);
long getTimeAt(int index);
@Deprecated()
default boolean tryLockNanosEntry(long nanos) {
return AcquisitionStrategies
.<ReadWriteLockingStrategy>spinLoop(nanos, TimeUnit.NANOSECONDS).acquire(
TryAcquireOperations.writeLock(),
VanillaReadWriteWithWaitsLockingStrategy.instance(),
checkedBytesStoreAccess(), ((Byteable) this).bytesStore(),
((Byteable) this).offset());
}
@Deprecated()
default void unlockEntry() throws IllegalMonitorStateException {
VanillaReadWriteWithWaitsLockingStrategy.instance()
.writeUnlock(checkedBytesStoreAccess(),
((Byteable) this).bytesStore(), ((Byteable) this).offset());
}
}
}