Skip to content

Commit

Permalink
Added synchronize function. (#437)
Browse files Browse the repository at this point in the history
* Added synchronize function.

This function synchronizes the code ran in the closure with any other
thread that has called the synchronize(name, closure) function using the
same name String (by value). For more info, read _synchronized.docs().

* synchronized() improvements.

- Changed syntax from synchronized(CString, CClosure) to
synchronized(Construct, <code>).
- Allow CArrays to synchronize by reference.
- Allow non-CString Constructs (except for CNull) to be used as values
for synchronization (Construct.val()).

* Added synchronized keyword for new syntax support.

Added syntax support for: synchronized(@syncObj) { code } -->
synchronized(@syncObject, code)

* Added example to synchronized().

Added an example.

* Make synchronized return void.

Instead of returning the evals() of the code inside, return void.

* Added readibility to synchronized() examples.

More lines, more pleasure for the human eye.
  • Loading branch information
Pieter12345 authored and LadyCailin committed Sep 17, 2016
1 parent 6ed1976 commit 73516fe
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
@@ -0,0 +1,17 @@
package com.laytonsmith.core.compiler.keywords;

import com.laytonsmith.core.compiler.Keyword;

/**
* A handler that changes synchronized(arg) { code } format to synchronized(arg, code).
* @author Pieter12345
*/
@Keyword.keyword("synchronized")
public class SynchronizedKeyword extends SimpleBlockKeywordFunction {

@Override
protected Integer[] getFunctionArgumentCount() {
return new Integer[]{1};
}

}
183 changes: 183 additions & 0 deletions src/main/java/com/laytonsmith/core/functions/Threading.java
Expand Up @@ -10,7 +10,10 @@
import com.laytonsmith.annotations.noboilerplate;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CClosure;
import com.laytonsmith.core.constructs.CNull;
Expand All @@ -21,13 +24,18 @@
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.GlobalEnv;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CRE.CREInsufficientArgumentsException;
import com.laytonsmith.core.exceptions.CRE.CRENullPointerException;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.CancelCommandException;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.FunctionReturnException;
import com.laytonsmith.core.exceptions.LoopManipulationException;
import com.laytonsmith.core.exceptions.ProgramFlowManipulationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

/**
Expand Down Expand Up @@ -329,4 +337,179 @@ public Version since() {

}

@api
@noboilerplate
@seealso({x_new_thread.class})
public static class _synchronized extends AbstractFunction {
private static final Map<Object, Integer> syncObjectMap = new HashMap<Object, Integer>();

@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRENullPointerException.class};
}

@Override
public boolean isRestricted() {
return true;
}

@Override
public Boolean runAsync() {
return false;
}

@Override
public boolean preResolveVariables() {
return false;
}

@Override
public boolean useSpecialExec() {
return true;
}

@Override
public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) {

// Get the sync object tree and the code to synchronize.
ParseTree syncObjectTree = nodes[0];
ParseTree code = nodes[1];

// Get the sync object (CArray or String value of the Construct).
Construct cSyncObject = parent.seval(syncObjectTree, env);
if(cSyncObject instanceof CNull) {
throw new CRENullPointerException("Synchronization object may not be null in " + getName() + "().", t);
}
Object syncObject;
if(cSyncObject instanceof CArray) {
syncObject = cSyncObject;
} else {
syncObject = cSyncObject.val();
}

// Add String sync objects to the map to be able to synchronize by value.
if(syncObject instanceof String) {
synchronized(syncObjectMap) {
searchLabel: {
for(Entry<Object, Integer> entry : syncObjectMap.entrySet()) {
Object key = entry.getKey();
if(key instanceof String && key.equals(syncObject)) {
syncObject = key; // Get reference, value of this assign is the same.
entry.setValue(entry.getValue() + 1);
break searchLabel;
}
}
syncObjectMap.put(syncObject, 1);
}
}
}

// Evaluate the code, synchronized by the passed sync object.
try {
synchronized(syncObject) {
parent.seval(code, env);
}
} catch(RuntimeException e) {
throw e;
} finally {

// Remove 1 from the call count or remove the sync object from the map if it was a sync-by-value.
if(syncObject instanceof String) {
synchronized(syncObjectMap) {
int count = syncObjectMap.get(syncObject); // This should never return null.
if(count <= 1) {
syncObjectMap.remove(syncObject);
} else {
for(Entry<Object, Integer> entry : syncObjectMap.entrySet()) {
if(entry.getKey() == syncObject) { // Equals by reference.
entry.setValue(count - 1);
break;
}
}
}
}
}
}
return CVoid.VOID;
}

@Override
public Construct exec(final Target t, final Environment env, Construct... args) throws ConfigRuntimeException {
return CVoid.VOID;
}

@Override
public String getName() {
return "synchronized";
}

@Override
public Integer[] numArgs() {
return new Integer[]{2};
}

@Override
public String docs() {
return "void {syncObject, code} Synchronizes access to the code block for all calls (from different"
+ " threads) with the same syncObject argument."
+ " This means that if two threads will call " + getName() + "('example', <code>), the second"
+ " call will hang the thread until the passed code of the first call has finished executing."
+ " If you call this function from within this function on the same thread using the same"
+ " syncObject, the code will simply be executed."
+ " For more information about synchronization, see:"
+ " https://en.wikipedia.org/wiki/Synchronization_(computer_science)";
}

@Override
public Version since() {
return CHVersion.V3_3_2;
}

@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Demonstrates two threads possibly overwriting eachother", ""
+ "export('log', '');\n"
+ "x_new_thread('Thread1', closure() {\n"
+ "\t@log = import('log');\n"
+ "\t@log = @log.'Some new log message from Thread1.\n'\n"
+ "\texport('log', @log);\n"
+ "});\n"
+ "x_new_thread('Thread2', closure() {\n"
+ "\t@log = import('log');\n"
+ "\t@log = @log.'Some new log message from Thread2.\n'\n"
+ "\texport('log', @log);\n"
+ "});\n"
+ "sleep(0.1);\n"
+ "msg(import('log'));",
"Some new log message from Thread1.\n"
+ "\nOR\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread1.\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n"),
new ExampleScript("Demonstrates two threads modifying the same variable without the possibility of"
+ " overwriting eachother because they are synchronized.", ""
+ "export('log', '');\n"
+ "x_new_thread('Thread1', closure() {\n"
+ "\tsynchronized('syncLog') {\n"
+ "\t\t@log = import('log');\n"
+ "\t\t@log = @log.'Some new log message from Thread1.\n'\n"
+ "\t\texport('log', @log);\n"
+ "\t}\n"
+ "});\n"
+ "x_new_thread('Thread2', closure() {\n"
+ "\tsynchronized('syncLog') {\n"
+ "\t\t@log = import('log');\n"
+ "\t\t@log = @log.'Some new log message from Thread2.\n'\n"
+ "\t\texport('log', @log);\n"
+ "\t}\n"
+ "});\n"
+ "sleep(0.1);\n"
+ "msg(import('log'));",
"Some new log message from Thread1.\nSome new log message from Thread2.\n"
+ "\nOR\nSome new log message from Thread2.\nSome new log message from Thread1.\n")
};
}

}

}

0 comments on commit 73516fe

Please sign in to comment.