Asynchronous tasks
#1
It is possible to run things asynchronly (in a other thread) and go back to synchronous (/ callbacks)?
I need this because I want to do database requests and one requests took 0.2 - 1.2ms.

My implementation in java:
package tv.rewinside.database.bukkit.util;

import com.google.common.collect.Queues;
import java.util.Queue;
import java.util.logging.Level;
import tv.rewinside.database.bukkit.DatabasePlugin;

public class DatabaseQueue extends Thread {
	private final Queue<Runnable> runnables = Queues.newConcurrentLinkedQueue();

	public DatabaseQueue() {
		super("Database Queue");
		this.setDaemon(true);
	}

	public synchronized void addRunnable(Runnable runnable) {
		this.runnables.add(runnable);
		this.notify();
	}

	private synchronized void blockCurrentThread() {
		try {
			while (this.runnables.isEmpty()) {
				this.wait();
			}
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
	}

	public int getCount() {
		return this.runnables.size();
	}

	@Override
	public void run() {
		while (!this.isInterrupted()) {
			this.blockCurrentThread();

			while (!this.runnables.isEmpty()) {
				try {
					this.runnables.poll().run();
				} catch (Exception ex) {
					DatabasePlugin.getInstance().getLogger().log(Level.WARNING, "Exception while execute database task", ex);
				}
			}
		}
	}

}

public interface DatabaseCallback<T> {

	public void run(T result);

}

And a example use of the queue:
public void getPlayer(final String uuid, final DatabaseCallback<DatabasePlayer> callback) {
		DatabasePlayer dbPlayer = this.cachedPlayers.get(uuid);
		if (dbPlayer != null) {
			callback.run(dbPlayer);
			return;
		}

		// Load player -> Async
		this.plugin.getQueue().addRunnable(new Runnable() {
			@Override
			public void run() {
				DatabaseCache.this.syncCallbackCall(callback, DatabaseCache.this.loadPlayer(uuid));
			}
		});
	}

private DatabasePlayer loadPlayer(String uuid) {
		DatabasePlayer dbPlayer = this.plugin.getConnection().getPlayer(uuid);
		if (dbPlayer == null) return null;

		this.cachedPlayers.put(uuid, dbPlayer);
		this.cachedPlayersFromName.put(dbPlayer.getLastName(), dbPlayer);
		return dbPlayer;
	}

private <T> void syncCallbackCall(final DatabaseCallback<T> callback, final T arg) {
		if (Bukkit.isPrimaryThread()) {
			callback.run(arg);
			return;
		}

		Bukkit.getScheduler().runTask(this.plugin, new Runnable() {
			@Override
			public void run() {
				callback.run(arg);
			}
		});
	}
Reply
Thanks given by:
#2
It isn't currently possible to create a new thread in Lua. Xoft did play around with it a while ago, but I wonder if:
  • If we could work with it in MCServer
  • If it could work with LuaJit if we would ever move to that.
Reply
Thanks given by:
#3
This is not possible with the current API. To implement the java API would involve significant development effort as we would need to implement a thread pool in C++. From the point of view of MCServer the simplest solution would be to implement a WebWorker style wrapper around cIsThread. However would this be too primitive an interface for plugin developers?
Reply
Thanks given by:
#4
Coming back to this, xoft do you think your LuaMultiThreaded could be implemented in MCServer? And if so, if I were to do an action that takes really long, for example a large for-loop, would it still block the server?
Reply
Thanks given by:
#5
It would need a lot of thought and probably a lot of testing. For example the threading might interfere with our callbacks, the objects currently guaranteed to be valid during the callback duration might get deallocated or another similar failure.

If if was implemented, a for loop would block the thread in which it was started. So if you started the for-loop in the world tick hook callback, bad idea. If you started it in the webadmin callback thread, no major problem there, the world goes on, only the webadmin will take ages to respond. It might even be possible to create a separate thread directly from Lua.
Reply
Thanks given by:
#6
It would be great if we could create new threads in Lua. That could allow WorldEdit for example to make big structures without blocking the server.
Reply
Thanks given by:
#7
Not likely, since writing the structure might still block the server for a considerable amount of time. True, much less than actually generating such a big structure.
Reply
Thanks given by:
#8
You could already do a few things to avoid lag - for example make the command create a description of the operation, store it in a storage and have the world tick thread do a little bit of work on the queue of such operations at a time - for example generating, say, 10 slices of a big area.
function OnCmdGenerate(...)
  local operation = cGenerateStructure:new(...)
  table.insert(g_WorkQueue[worldName], operation)
end

function OnWorldTick(...)
  local workItem = g_WorkQueue[worldName][1]
  if (workItem ~= nil) then
    workItem:doABitOfWork()
    if (workItem:IsFinished()) then
      table.remove(g_WorkQueue, 1)
    end
  end
end
Reply
Thanks given by:
#9
That would require allot of work though Wink If we could create a new thread for a certain function, then it should be really easy to make WorldEdit use it Smile
Reply
Thanks given by:
#10
The API I suggested earlier get rid of a lot of these issues. You basically create a whole separate Lua State which gets a file passed to it process. It can only communicate with the parent via asynchronous message passing.
Reply
Thanks given by:




Users browsing this thread: 1 Guest(s)