In our cadence workflow we often need to wait a certain amount of time for external events before continuing (ie Email read, link clicked, etc..).
I was wondering what was the best way to notify our workflows of these events. Are signals the right way, or should we create an activity that would wait for the event?
From what I've seen we need to create a signal channel ch := workflow.GetSignalChannel(ctx, SignalName), however the context is not available in activities.
Signaling is the recommended way for sending events to workflows.
The frequently used pattern for Go workflows is to use Selector to wait for multiple signal channels as well as a timer future.
Go sample:
sig1Ch := workflow.GetSignalChannel(ctx, "signal1")
sig2Ch := workflow.GetSignalChannel(ctx, "signal2")
timeout := workflow.NewTimer(ctx, time.Minute * 30)
s := workflow.NewSelector(ctx)
var signal1 *Signal1Struct
var signal2 *Signal2Struct
s.AddFuture(timeout, func(f Future) {
})
s.AddReceive(sig1Ch, func(c Channel, more bool) {
c.Receive(ctx, signal1)
})
s.AddReceive(sig2Ch, func(c Channel, more bool) {
c.Receive(ctx, signal2)
})
s.Select(ctx)
if signal1 == nil && signal2 == nil {
// handle timeout
} else {
// process signals
}
Java sample:
public interface MyWorkflow {
#WorkflowMethod
void main();
#SignalMethod
void signal1(Signal1Struct signal);
#SignalMethod
void signal2(Signal2Struct signal);
}
public class MyWorkflowImpl implements MyWorkflow {
private Signal1Struct signal1;
private Signal2Struct signal2;
#Override
public void main() {
Workflow.await(Duration.ofMinutes(30),
() -> signal1 != null || signal2 != null);
if (signal1 == null && signal2 == null) {
// handle timeout
}
// process signals
}
#Override
public void signal1(Signal1Struct signal) {
signal1 = signal;
}
#Override
public void signal2(Signal2Struct signal) {
signal2 = signal;
}
}
Note that it is a good idea to account for the workflow worker outages. For example let's imagine that the above workflow is started and a signal is received 40 minutes after the start while all workflow workers are down. In this case when workers are brought back both timeout future and signCh would be not empty. As Selector doesn't guarantee ordering it is possible that the signal is delivered before the timer even if it was received after. So your code logic should account for this. For example there is a hard requirement that a signal received after the 30 minutes since the workflow start must be ignored. Then the above sample has to be modified to:
Go sample:
...
start := workflow.Now(ctx); // must use workflow clock
s.Select(ctx)
duration := workflow.Now(ctx).Sub(start)
if duration.Minutes() >= 30 || (signal1 == nil && signal2 == nil) {
// handle timeout
} else {
// process signals
}
Java sample:
public void main() {
long start = Workflow.currentTimeMillis(); // must use workflow clock
Duration timeout = Duration.ofMinutes(30);
Workflow.await(timeout, () -> signal1 != null || signal2 != null);
long duration = Workflow.currentTimeMillis() - start;
if (timeout.toMillis() <= duration || (signal1 == null && signal2 == null)) {
// handle timeout
}
// process signals
}
The updated code behaves correctly even if the workflow execution was delayed for an hour.
Edit: Added signal sending sample
Go sample:
c, err := client.NewClient(client.Options{
HostPort: client.DefaultHostPort,
})
if err != nil {
log.Fatalln("Unable to create client", err)
}
defer c.Close()
err := c.SignalWorkflow(context.Background(), <workflowId>, "", "signal1", Signal1Struct{})
Java sample:
WorkflowServiceStubs service = WorkflowServiceStubs.newInstance();
WorkflowClient client = WorkflowClient.newInstance(service);
GreetingWorkflow myWorkflow =
client.newWorkflowStub(MyWorkflow.class, <workflowId>);
myWorkflow.signal1(new Signal1Struct());
Related
I am writing a test program to explore the use of Isolates in Dart/Flutter. One type of isolate that I have is started and stopped using a switch on a Flutter UI. This sends a message (START and STOP) to the isolate and I use a ValueNotifier to detect these commands and respond to them. When I initially wrote this, the Isolate ran continuously and didn’t respond the to the STOP command, which I understand is because the event queue would never be empty to process it.
Based on the first answer to this thread...
How to terminate a long running isolate #2
… and the suggested approach on this blog page:
https://hackernoon.com/executing-heavy-tasks-without-blocking-the-main-thread-on-flutter-6mx31lh
… I have tried to break my code up using Futures. I have split my run block into runWorker() and runChunk() functions, with runChunk called repeatedly as long as the Isolate should be running (run == true). I am doing something wrong because the Isolate still runs away and does not process the STOP command. I get slightly different results depending on whether I call runChunk directly or using Future.delayed (as per the hackernoon blog page), but neither approach works.
I believe that the STOP code works because if I remove the processing loop then everything triggers as expected, and I had an earlier version of this that worked when I included a 'Future.delayed' of 1 microsecond between each counter loop. So assume I am just using Futures incorrectly and not freeing up the event queue between 'runChunk' calls.
Can anyone tell me what I am doing wrong here? Here is the code for my isolate...
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/material.dart';
class ContinuousIsolator {
ContinuousIsolator(
{required int channel, required void Function(double) setCounter}) {
print('Isolator initialisation');
_channel = channel;
_setCounter = setCounter;
spawn();
}
late int _channel; // The id of this isolate
late void Function(double) _setCounter;
late SendPort _port;
late Isolate _isolate;
ReceivePort receivePort = ReceivePort();
// Spawn a new isolate to complete the countdown (or up)
// channel = the number of the isolate
// counter = the value to count down from (or up to)
void spawn() {
print('Isolator establishing receiver');
receivePort.listen((msg) {
// print('Isolator message received');
// Unpack the map from the returned string
Map<int, dynamic> map = Map<int, dynamic>.from(msg);
// There should be only one key:value pair
for (var key in map.keys) {
msg = map[key]; // Extract the message
}
// print('Channel $_channel received "$msg" of type ${msg.runtimeType}');
// If we have received a Sendport, then add it to the port map
if (msg is SendPort) {
_port = msg;
} else {
// Otherwise process the message
// If it contains 'END' then we need to terminate the isolate
switch (msg) {
case 'END':
_isolate.kill();
// Isolate has completed, then close this receiver port
receivePort.close();
break;
default:
_setCounter(msg); // Send message to display
break;
}
}
});
// Start the isolate then let's get working on the countdown timer
Isolate.spawn(worker, {_channel: receivePort.sendPort}).then((isolate) {
_isolate = isolate; // Capture isolate so we can kill it later
});
}
void run() {
print('Sending START to worker');
_port.send('START');
}
void stop() {
print('Sending STOP to worker');
_port.send('STOP');
}
void end() {
_port.send('END'); // Send counter value to start countdown
}
}
void worker(Map<int, dynamic> args) {
int? id; // Number for this channel
ReceivePort receivePort = ReceivePort(); // Receive port for
SendPort? sendPort;
ValueNotifier<String> message = ValueNotifier('');
const double start = 10000000;
double counter = start;
const int chunkSize = 1000;
bool down = true;
bool run = true;
// Unpack the args to get the id and sendPort.
// There should be only one key:value pair
dynamic msg = '';
Map<int, dynamic> map = Map<int, dynamic>.from(args);
for (var key in map.keys) {
id = key; // Extract the isolate id
msg = map[key]; // Extract the message
}
// First message should contain the receivePort for the main isolate
if (msg is SendPort) {
sendPort = msg;
// print('args: $args port: $sendPort');
print('worker $id sending send port');
sendPort.send({id: receivePort.sendPort});
}
double getCounter() {
return counter;
}
void setCounter(double value) {
counter = value;
}
bool getDown() {
return down;
}
void setDown(bool value) {
down = value;
}
Future runChunk(
int chunkSize,
bool Function() getDown,
void Function(bool) setDown,
double Function() getCounter,
void Function(double) setCounter) {
const double start = 10000000;
print('Running chunk, counter is ${getCounter()}');
for (int i = 0; i < chunkSize; i++) {
// print('Worker $id in the while loop');
if (getDown() == true) {
setCounter(getCounter() - 1);
if (getCounter() < 0) setDown(!getDown());
} else {
setCounter(getCounter() + 1);
if (getCounter() > start) setDown(!getDown());
}
if ((getCounter() ~/ 1000) == getCounter() / 1000) {
// print('Continuous Counter is ${getCounter()}');
sendPort!.send({id: getCounter()});
} // Send the receive port
}
return Future.value();
}
void changeMessage() {
print('Message has changed to ${message.value}');
if (message.value == 'START') {
run = true;
} else {
run = false;
}
}
void runWorker() async {
message.addListener(changeMessage);
print('Worker running counter down from $counter');
while (run == true) {
// This line appears to run the isolate, but there is no output/feedback to the GUI
// The STOP command does not interrupt operation.
Future.delayed(const Duration(microseconds: 0),
() => runChunk(chunkSize, getDown, setDown, getCounter, setCounter));
// This line runs the isolate with feedback to the GUI
// The STOP command does not interrupt operation.
runChunk(chunkSize, getDown, setDown, getCounter, setCounter);
}
message.removeListener(changeMessage);
}
// Establish listener for messages from the controller
print('worker $id establishing listener');
receivePort.listen((msg) {
print('worker $id has received $msg');
switch (msg) {
case 'START':
print('Worker $id starting run');
message.value = msg;
runWorker();
break;
case 'STOP':
print('Worker $id stopping run');
message.value = msg;
break;
case 'END':
message.removeListener(changeMessage);
receivePort.close;
break;
default:
break;
}
});
}
Ok, so it turns out that the problem was not with the Futures, but with this line of code in the runWorker() method:
while (run == true) {
... which (I think) was blocking the event queue.
I also think I was over-thinking things using a ValueNotifier (although I don't think there is any reason why this wouldn’t work).
I have simplified my code so that the main computation method (runChunk) checks if it should still be running after each chunk, and if the answer is yes then it just calls itself again to run the next chunk.
The run flag is set to true and runChunk called when START is received from the parent and the run flag is set to false when STOP is received. This removes the need for the ValueNotifier.
Everything works as expected now and the isolate computation (‘runChunk’) can be started and stopped on demand.
Here is the revised code (the print lines are debug lines that I have left in):
import 'dart:async';
import 'dart:isolate';
class ContinuousIsolator {
ContinuousIsolator(
{required int channel, required void Function(double) setCounter}) {
print('Isolator initialisation');
_channel = channel;
_setCounter = setCounter;
spawn();
}
late int _channel; // The id of this isolate
late void Function(double)
_setCounter; // Callback function to set on-screen counter
late SendPort _port; // SendPort of child isolate to communicate with
late Isolate _isolate; // Pointer to child isolate
ReceivePort receivePort = ReceivePort(); // RecevierPort for this class
// Spawn a new isolate to complete the countdown (or up)
// channel = the number of the isolate
// counter = the value to count down from (or up to)
void spawn() {
print('Isolator establishing receiver');
// Establish a listener for messages from the child
receivePort.listen((msg) {
// Unpack the map from the returned string (child sends a single map
// contained key: isolate id and value: message)
Map<int, dynamic> map = Map<int, dynamic>.from(msg);
// There should be only one key:value pair received
for (var key in map.keys) {
msg = map[key]; // Extract the message
}
// If we have received a Sendport, then capture it to communicate with
if (msg is SendPort) {
_port = msg;
} else {
// Otherwise process the message
// If it contains 'END' then we need to terminate the isolate
switch (msg) {
case 'END':
_isolate.kill();
// Isolate has completed, then close this receiver port
receivePort.close();
break;
default:
_setCounter(msg); // Send message to display
break;
}
}
});
// Start the child isolate
Isolate.spawn(worker, {_channel: receivePort.sendPort}).then((isolate) {
_isolate = isolate; // Capture isolate so we can kill it later
});
}
// Class method to start the child isolate doing work (countdown timer)
void run() {
print('Sending START to worker');
_port.send('START');
}
// Class method to stop the child isolate doing work (countdown timer)
void stop() {
print('Sending STOP to worker');
_port.send('STOP');
}
// Class method to tell the child isolate to self-terminate
void end() {
_port.send('END'); // Send counter value to start countdown
}
}
// Child isolate function that is spawned by the parent class ContinuousIsolator
// Called initially with single map of key: 'unique channel id' and value:
// receiver port from the parent
void worker(Map<int, dynamic> args) {
int? id; // Unique id number for this channel
ReceivePort receivePort = ReceivePort(); // Receive port for this isolate
SendPort? sendPort; // Send port to communicate with the parent
const double start = 10000000; // Starting counter value
double counter = start; // The counter
const int chunkSize =
100; // The number of counter decrements/increments to process per 'chunk'
bool down = true; // Flag to show is counting 'down' (true) or 'up' (false)
bool run = false; // Flag to show if the isolate is running the computation
// Unpack the initial args to get the id and sendPort.
// There should be only one key:value pair
dynamic msg = '';
Map<int, dynamic> map = Map<int, dynamic>.from(args);
for (var key in map.keys) {
id = key; // Extract the isolate id
msg = map[key]; // Extract the message
}
// The first message should contain the receivePort for the main isolate
if (msg is SendPort) {
sendPort = msg; // Capture sendport to communicate with the parent
print('worker $id sending send port');
// Send the receiver port for this isolate to the parent
sendPort.send({id: receivePort.sendPort});
}
// Method to get the current counter value
double getCounter() {
return counter;
}
// Method to set the current counter value
void setCounter(double value) {
counter = value;
}
// Method to get the down flag value
bool getDown() {
return down;
}
// Method to set the down flag value
void setDown(bool value) {
down = value;
}
// This function does the main work of the isolate, ie the computation
Future<void> runChunk(
int chunkSize, // The number of loops to process for a given 'chunk'
bool Function() getDown, // Callback to get bool down value
void Function(bool) setDown, // Callback to set bool down value
double Function() getCounter, // Call back to get current counter value
void Function(double) setCounter) // Callback to set counter value
async {
const double start = 10000000; // Starting value for the counter
// Count down (or up) the counter for chunkSize iterations
for (int i = 0; i < chunkSize; i++) {
// Counting down...
if (getDown() == true) {
setCounter(getCounter() - 1);
// If reached zero, flip the counting up
if (getCounter() < 0) setDown(!getDown());
} else {
// Counting up...
setCounter(getCounter() + 1);
// If reached start (max), flip the counting down
if (getCounter() > start) setDown(!getDown());
}
// Update the display every 1000 points
if ((getCounter() ~/ 1000) == getCounter() / 1000) {
sendPort!.send({id: getCounter()}); // Notify parent of the new value
}
}
// If the isolate is still running (parent hasn't sent 'STOP') then
// call this function again to iterate another chunk. This gives the event
// queue a chance to process the 'STOP'command from the parent
if (run == true) {
// I assume Future.delayed adds call back onto the event queue
Future.delayed(const Duration(microseconds: 0), () {
runChunk(chunkSize, getDown, setDown, getCounter, setCounter);
});
}
}
// Establish listener for messages from the controller
print('worker $id establishing listener');
receivePort.listen((msg) {
print('worker $id has received $msg');
switch (msg) {
case 'START':
// Start the worker function running and set run = true
print('Worker $id starting run');
run = true;
runChunk(chunkSize, getDown, setDown, getCounter, setCounter);
break;
case 'STOP':
// Set run = false to stop the worker function
print('Worker $id stopping run');
run = false;
break;
case 'END':
// Inform parent that isolate is shutting down
sendPort?.send({id: msg});
receivePort.close; // Close the receiver port
break;
default:
break;
}
});
}
I'm trying to make a Photon Bolt game that connects two devices. The problem is that the Client tends to get disconnected a lot, an it doesn't reconnect automatically. I've tried using methods like ReconnectAndRejoin, but it seems like it only works in PUN. Right now I'm using this custom solution, without success:
[BoltGlobalBehaviour(BoltNetworkModes.Client)]
public class InitialiseGameClient : Photon.Bolt.GlobalEventListener
{
private bool disconnected;
public void Update(){
if(disconnected){
Reconnect();
}
}
public override void Disconnected(BoltConnection connection)
{
disconnected = true;
}
public void Reconnect(){
BoltLauncher.StartClient();
PlayerPrefs.DeleteAll();
if (BoltNetwork.IsRunning && BoltNetwork.IsClient)
{
foreach (var session in BoltNetwork.SessionList)
{
UdpSession udpSession = session.Value as UdpSession;
if (udpSession.Source != UdpSessionSource.Photon)
continue;
PhotonSession photonSession = udpSession as PhotonSession;
string sessionDescription = String.Format("{0} / {1} ({2})",
photonSession.Source, photonSession.HostName, photonSession.Id);
RoomProtocolToken token = photonSession.GetProtocolToken() as RoomProtocolToken;
if (token != null)
{
sessionDescription += String.Format(" :: {0}", token.ArbitraryData);
}
else
{
object value_t = -1;
object value_m = -1;
if (photonSession.Properties.ContainsKey("t"))
{
value_t = photonSession.Properties["t"];
}
if (photonSession.Properties.ContainsKey("m"))
{
value_m = photonSession.Properties["m"];
}
sessionDescription += String.Format(" :: {0}/{1}", value_t, value_m);
}
ServerConnectToken connectToken = new ServerConnectToken
{
data = "ConnectTokenData"
};
Debug.Log((int)photonSession.Properties["t"]);
var propertyID = PlayerPrefs.GetInt("PropertyID", 2);;
if((int)photonSession.Properties["t"] == propertyID){
BoltMatchmaking.JoinSession(photonSession, connectToken);
disconnected = false;
}
}
}
}
}
With this method I'm trying to use the same code used to connect the the client for the first time in the reconnect function, and keep trying until the client manages to connect. However it seems that the code never executes, even if the disconnect function gets triggered (the reconnect doesn't). Is there any Bolt integrated function that helps with reconnecting? Thanks in advance.
You need to shutdown bolt, then try reconnecting. Even if you don't get the below exception, it's just an example and you should shutdown and do BoltLauncher.StartClient() etc.
BoltException: Bolt is already running, you must call BoltLauncher.Shutdown() before starting a new instance of Bolt.
In my processor API I store the messages in a key value store and every 100 messages I make a POST request. If something fails while trying to send the messages (api is not responding etc.) I want to stop processing messages. Until there is evidence the API calls work.
Here is my code:
public class BulkProcessor implements Processor<byte[], UserEvent> {
private KeyValueStore<Integer, ArrayList<UserEvent>> keyValueStore;
private BulkAPIClient bulkClient;
private String storeName;
private ProcessorContext context;
private int count;
#Autowired
public BulkProcessor(String storeName, BulkClient bulkClient) {
this.storeName = storeName;
this.bulkClient = bulkClient;
}
#Override
public void init(ProcessorContext context) {
this.context = context;
keyValueStore = (KeyValueStore<Integer, ArrayList<UserEvent>>) context.getStateStore(storeName);
count = 0;
// to check every 15 minutes if there are any remainders in the store that are not sent yet
this.context.schedule(Duration.ofMinutes(15), PunctuationType.WALL_CLOCK_TIME, (timestamp) -> {
if (count > 0) {
sendEntriesFromStore();
}
});
}
#Override
public void process(byte[] key, UserEvent value) {
int userGroupId = Integer.valueOf(value.getUserGroupId());
ArrayList<UserEvent> userEventArrayList = keyValueStore.get(userGroupId);
if (userEventArrayList == null) {
userEventArrayList = new ArrayList<>();
}
userEventArrayList.add(value);
keyValueStore.put(userGroupId, userEventArrayList);
if (count == 100) {
sendEntriesFromStore();
}
}
private void sendEntriesFromStore() {
KeyValueIterator<Integer, ArrayList<UserEvent>> iterator = keyValueStore.all();
while (iterator.hasNext()) {
KeyValue<Integer, ArrayList<UserEvent>> entry = iterator.next();
BulkRequest bulkRequest = new BulkRequest(entry.key, entry.value);
if (bulkRequest.getLocation() != null) {
URI url = bulkClient.buildURIPath(bulkRequest);
try {
bulkClient.postRequestBulkApi(url, bulkRequest);
keyValueStore.delete(entry.key);
} catch (BulkApiException e) {
logger.warn(e.getMessage(), e.fillInStackTrace());
}
}
}
iterator.close();
count = 0;
}
#Override
public void close() {
}
}
Currently in my code if a call to the API fails it will iterate the next 100 (and this will keep happening as long as it fails) and add them to the keyValueStore. I don't want this to happen. Instead I would prefer to stop the stream and continue once the keyValueStore is emptied. Is that possible?
Could I throw a StreamsException?
try {
bulkClient.postRequestBulkApi(url, bulkRequest);
keyValueStore.delete(entry.key);
} catch (BulkApiException e) {
throw new StreamsException(e);
}
Would that kill my stream app and so the process dies?
You should only delete the record from state store after you make sure your record is successfully processed by the API, so remove the first keyValueStore.delete(entry.key); and keep the second one. If not then you can potentially lost some messages when keyValueStore.delete is committed to underlying changelog topic but your messages are not successfully process yet, so it's only at most one guarantee.
Just wrap the calling API code around an infinite loop and keep trying until the record successfully processed, your processor will not consume new message from above processor node cause it's running in a same StreamThread:
private void sendEntriesFromStore() {
KeyValueIterator<Integer, ArrayList<UserEvent>> iterator = keyValueStore.all();
while (iterator.hasNext()) {
KeyValue<Integer, ArrayList<UserEvent>> entry = iterator.next();
//remove this state store delete code : keyValueStore.delete(entry.key);
BulkRequest bulkRequest = new BulkRequest(entry.key, entry.value);
if (bulkRequest.getLocation() != null) {
URI url = bulkClient.buildURIPath(bulkRequest);
while (true) {
try {
bulkClient.postRequestBulkApi(url, bulkRequest);
keyValueStore.delete(entry.key);//only delete after successfully process the message to achieve at least one processing guarantee
break;
} catch (BulkApiException e) {
logger.warn(e.getMessage(), e.fillInStackTrace());
}
}
}
}
iterator.close();
count = 0;
}
Yes you could throw a StreamsException, this StreamTask will be migrate to another StreamThread during re-balance, maybe on the sample application instance. If the API keep causing Exception until all StreamThread had died, your application will not automatically exit and receive below Exception, you should add a custom StreamsException handler to exit your app when all stream threads had died using KafkaStreams#setUncaughtExceptionHandler or listen to Stream State change (to ERROR state):
All stream threads have died. The instance will be in error state and should be closed.
In the end I used a simple KafkaConsumer instead of KafkaStreams, but the bottom line was that I changed the BulkApiException to extend RuntimeException, which I throw again after I log it. So now it looks as follows:
} catch (BulkApiException bae) {
logger.error(bae.getMessage(), bae.fillInStackTrace());
throw new BulkApiException();
} finally {
consumer.close();
int exitCode = SpringApplication.exit(ctx, () -> 1);
System.exit(exitCode);
}
This way the application is exited and the k8s restarts the pod. That was because if the api where I'm trying to forward the requests is down, then there is no point on continue reading messages. So until the other api is back up k8s will restart a pod.
We have batch files that my company likes to run overnight so I took our server(MatLab)/client(Java/Eclispe) code, that worked fine with single files, put a while true loop around everything and got it to work properly that way. The only problem we have it that the server always looks for a client, with the socket.accept() call, but if it has no clients to connect to it, it just sits there forever. To close the program we have to go to the task manager and force it closed.
So it there any way I could put a timer on accept so if no one tries to connect after a certain time, no more batch files to run, I can cancel the connect and shutdown the program.
This code will allow you to set the timeout on accept()
private ServerSocket listener;
private int timeout;
private Thread runner;
private boolean canceled;
...
// returns true if cancel signal has been received
public synchronized boolean isCanceled()
{
return canceled;
}
// returns true if this call does the canceling
// or false if it has already been canceled
public synchronized boolean cancel()
{
if ( canceled ) {
// already canceled due to previous caller
return false;
}
canceled = true;
runner.interrupt();
return true;
}
public void run()
{
// to avoid race condition (see below)
listener.setSoTimeout(timeout);
while ( ! isCanceled() ) {
// DANGER!!
try {
Socket client = listener.accept();
// hand client off to worker thread...
}
catch ( SocketTimeoutException e ) {
// ignore and keep looping
}
catch ( InterruptedIOException e ) {
// got signal while waiting for connection request
break;
}
}
try {
listener.close();
}
catch ( IOException e ) {
// ignore; we're done anyway
}
}
So...
I'm creating a plugin.
I have a main Class called Basics
Globally in Basics I create:
static Timer enterdungeon = new Timer();
static Timer finddungeon = new Timer();
static Timer lootdungeon = new Timer();
Also I have a class named task
the enterdungeon timer is a fixed period of time, and seems to work as expected when used.
As is the same for thee lootdungeon timer.
The finddungeon timer can be interrupted IF an event in basics is triggered.
The event DOES trigger fine
the top line in this event is:
finddungeon.cancel();
after it starts the lootdungeon timer.
the problem is the finddungeon timer does not cancel, it continues to run, below is the task class:
import java.util.TimerTask;
import me.boduzapho.Basics.DoWarp.Returner;
import org.bukkit.entity.Player;
public class task extends TimerTask
{
private final Player _player;
private final int ticks;
private int cnt = 0;
private final int _sec;
private final String _message;
public task(Player player, int sec, String message)
{
this._player = player;
this._sec = sec;
this._message = message;
this.ticks = sec;
}
private void timetoloot(Player p)
{
p.sendMessage("SUCCESS! Nice Job, Enjoy the loot!");
Returner loc1 = DoWarp.getwarp("launch", Basics.warps, Basics.wx,Basics.wy, Basics.wz, p);
DoWarp.warpme(loc1.x, loc1.y, loc1.z, p, false, Basics.plugin);
}
private void failedwhiteblock(Player p)
{
p.sendMessage("FAIL! You did not find the white block. Sending you back. TRY AGAIN!");
Returner loc1 = DoWarp.getwarp("launch", Basics.warps, Basics.wx, Basics.wy, Basics.wz, p);
DoWarp.warpme(loc1.x, loc1.y, loc1.z, p, false, Basics.plugin);
}
private void enterdungeon(Player p)
{
Basics.Stage.setLine(3, "Off you Go!");
Basics.Stage.update();
Basics.Stage.setLine(0, "");
Basics.Stage.setLine(1, "");
Basics.Stage.setLine(2, "");
Basics.Stage.setLine(3, "");
Basics.Stage.update();
Basics.cDoClear(p);
Basics.cDoSpawners(p);
Basics.cDoRed(p);
Returner loc1 = DoWarp.getwarp("dstart", Basics.warps, Basics.wx, Basics.wy, Basics.wz, p);
DoWarp.warpme(loc1.x, loc1.y, loc1.z, p, false, Basics.plugin);
Basics.DungeonPlayer = p;
p.sendMessage("Welcome to the Dungeon, you have 1 minuite to locate and click the white block.");
p.sendMessage("If you fail you will be returned to spawn. If you find it the treasures will be revieled");
p.sendMessage("and the monsters banished for 1 min so you can loot the chests! After which you will");
p.sendMessage("Be warped back to spawn with your Loot!");
Basics.finddungeon.schedule(new task(_player, 30, "Time left to find the WHITE block :"), 0, 1000);
Basics.enterdungeon.cancel();
}
#Override
public void run()
{
while (cnt < ticks)
{
try
{
Thread.sleep(1 * 1000);
_player.sendMessage(_message + " " + Integer.toString(_sec - cnt));
++cnt;
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
_player.sendMessage("Done!");
if (_message == "Time left:")
{
enterdungeon(_player);
}
if (_message == "Time left to find the WHITE block :")
{
failedwhiteblock(_player);
}
if (_message == "Time left to LOOT:")
{
timetoloot(_player);
}
//
return;
}
}
Here is the function called in Basics (main class) that is supposed to cancel the finddungeon timer.
// white block in dungeon
if (DungeonPlayer == player)
{
if ((block != null) && (block.getType() == Material.WOOL))
{
player.sendMessage("Canceling finddungeon from Basics");
finddungeon.cancel();
cDoClear(player);
cDoChests(player);
player.sendMessage("Congradulations! Time to Loot your rewards for finding the White Block!");
Timer lootdungeon = new Timer();
lootdungeon.schedule(new task(player, 10, "Time left to LOOT:"), 0, 1000);
return;
// ***
}
}
Can anyone shed any light on this?
Cause TimerTask.cancel doesn't do anything to the active task, it just clears the scheduler. You'll have to override cancel method, or just use this as a starting point:
class MyTimerTask extends TimerTask {
private volatile Thread thread;
#Override
public void run() {
thread = Thread.currentThread();
//do your task and keep your eye on InterruptedException when doing Sleeps, Waits
//also check Thread.interrupted()
}
public boolean cancel() {
Thread thread = this.thread;
if (thread != null) {
thread.interrupt();
}
return super.cancel();
}
}