Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 57 additions & 34 deletions src/pocketmine/scheduler/AsyncTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@
* with no AsyncTask running. Therefore, an AsyncTask SHOULD NOT execute for more than a few seconds. For tasks that
* run for a long time or infinitely, start another {@link \pocketmine\Thread} instead.
*
* WARNING: Do not call PocketMine-MP API methods, or save objects (and arrays containing objects) from/on other Threads!!
* WARNING: Any non-Threaded objects WILL BE SERIALIZED when assigned to members of AsyncTasks or other Threaded object.
* If later accessed from said Threaded object, you will be operating on a COPY OF THE OBJECT, NOT THE ORIGINAL OBJECT.
* If you want to store non-serializable objects to access when the task completes, store them using
* {@link AsyncTask#storeLocal}.
*
* WARNING: As of pthreads v3.1.6, arrays are converted to Volatile objects when assigned as members of Threaded objects.
* Keep this in mind when using arrays stored as members of your AsyncTask.
*
* WARNING: Do not call PocketMine-MP API methods from other Threads!!
Copy link
Copy Markdown
Member

@Muqsit Muqsit Aug 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Does "other Threads" include the main thread? Because I call NBT::readCompressed() and Item::nbtDeserialize() in one of my plugins asynchronously.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of things are not initialized on workers that are initialized on the main thread, which may cause unexpected behaviour. For example, because the ItemFactory is not initialized, calling nbtDeserialize() on a worker will give you a generic Item object no matter what item was stored.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh noes... I messed up in many places.

*/
abstract class AsyncTask extends Collectable{

Expand All @@ -51,29 +59,6 @@ abstract class AsyncTask extends Collectable{

private $crashed = false;

/**
* Constructs a new instance of AsyncTask. Subclasses don't need to call this constructor unless an argument is to be passed. ONLY construct this class from the main thread.
* <br>
* If an argument is passed into this constructor, it will be stored in a thread-local storage (in ServerScheduler), which MUST be retrieved through {@link #fetchLocal} when {@link #onCompletion} is called.
* Otherwise, a NOTICE level message will be raised and the reference will be removed after onCompletion exits.
* <br>
* If null or no argument is passed, do <em>not</em> call {@link #fetchLocal}, or an exception will be thrown.
* <br>
* WARNING: Use this method carefully. It might take a long time before an AsyncTask is completed. PocketMine will keep a strong reference to objects passed in this method.
* This may result in a light memory leak. Usually this does not cause memory failure, but be aware that the object may be no longer usable when the AsyncTask completes.
* (E.g. a {@link \pocketmine\Level} object is no longer usable because it is unloaded while the AsyncTask is executing, or even a plugin might be unloaded)
* Since PocketMine keeps a strong reference, the objects are still valid, but the implementation is responsible for checking whether these objects are still usable.
*
* @param mixed $complexData the data to store, pass null to store nothing. Scalar types can be safely stored in class properties directly instead of using this thread-local storage.
*/
public function __construct($complexData = null){
if($complexData === null){
return;
}

Server::getInstance()->getScheduler()->storeLocalComplex($this, $complexData);
}

public function run(){
$this->result = null;

Expand Down Expand Up @@ -216,14 +201,44 @@ public function onProgressUpdate(Server $server, $progress){
}

/**
* Call this method from {@link AsyncTask#onCompletion} to fetch the data stored in the constructor, if any, and
* clears it from the storage.
* Saves mixed data in ServerScheduler's object storage on the main thread. You may use this to retain references to
* objects or arrays which you need to access in {@link AsyncTask#onCompletion} which cannot be stored as a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onCompletion() or onProgressUpdate().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best not to introduce more confusion over fetch vs peek. This documents behaviour and the most common use, it's up to developers to decide how to use it.

* property of your task.
*
* Scalar types can be stored directly in class properties instead of using this storage.
*
* Objects stored in this storage MUST be retrieved through {@link #fetchLocal} when {@link #onCompletion} is called.
* Otherwise, a NOTICE level message will be raised and the reference will be removed after onCompletion exits.
*
* Do not call this method from {@link AsyncTask#onProgressUpdate}, because this method deletes the data and cannot
* be used in the next {@link AsyncTask#onProgressUpdate} call or from {@link AsyncTask#onCompletion}. Use
* {@link AsyncTask#peekLocal} instead.
* WARNING: Use this method carefully. It might take a long time before an AsyncTask is completed. PocketMine will
* keep a strong reference to objects passed in this method. This may result in a light memory leak. Usually this
* does not cause memory failure, but be aware that the object may be no longer usable when the AsyncTask completes.
* (E.g. a {@link \pocketmine\Level} object is no longer usable because it is unloaded while the AsyncTask is
* executing, or even a plugin might be unloaded). Since PocketMine keeps a strong reference, the objects are still
* valid, but the implementation is responsible for checking whether these objects are still usable.
*
* @param Server $server default null
* WARNING: THIS METHOD SHOULD ONLY BE CALLED FROM THE MAIN THREAD!
*
* @param mixed $complexData the data to store
*/
protected function storeLocal($complexData){
Server::getInstance()->getScheduler()->storeLocalComplex($this, $complexData);
}

/**
* Returns mixed data stored in the ServerScheduler's object store and clears it. Call this method from
* {@link AsyncTask#onCompletion} to fetch the data stored in the object store, if any.
*
* If no data was stored in the local store, or if the data was already retrieved by a previous call to fetchLocal,
* do NOT call this method, or an exception will be thrown.
*
* Do not call this method from {@link AsyncTask#onProgressUpdate}, because this method deletes stored data, which
* means that you will not be able to retrieve it again afterwards. Use {@link AsyncTask#peekLocal} instead to
* retrieve stored data without removing it from the store.
*
* WARNING: THIS METHOD SHOULD ONLY BE CALLED FROM THE MAIN THREAD!
*
* @param Server|null $server
*
* @return mixed
*
Expand All @@ -232,20 +247,26 @@ public function onProgressUpdate(Server $server, $progress){
protected function fetchLocal(Server $server = null){
if($server === null){
$server = Server::getInstance();
assert($server !== null, "Call this method only from the main thread!");
if($server === null){
throw new \BadMethodCallException("Stored objects can only be retrieved from the main thread");
}
}

return $server->getScheduler()->fetchLocalComplex($this);
}

/**
* Call this method from {@link AsyncTask#onProgressUpdate} to fetch the data stored in the constructor.
* Returns mixed data stored in the ServerScheduler's object store **without clearing** it. Call this method from
* {@link AsyncTask#onProgressUpdate} to fetch the data stored if you need to be able to access the data later on,
* such as in another progress update.
*
* Use {@link AsyncTask#peekLocal} instead from {@link AsyncTask#onCompletion}, because this method does not delete
* the data, and not clearing the data will result in a warning for memory leak after {@link AsyncTask#onCompletion}
* finished executing.
*
* @param Server|null $server default null
* WARNING: THIS METHOD SHOULD ONLY BE CALLED FROM THE MAIN THREAD!
*
* @param Server|null $server
*
* @return mixed
*
Expand All @@ -254,7 +275,9 @@ protected function fetchLocal(Server $server = null){
protected function peekLocal(Server $server = null){
if($server === null){
$server = Server::getInstance();
assert($server !== null, "Call this method only from the main thread!");
if($server === null){
throw new \BadMethodCallException("Stored objects can only be peeked from the main thread");
}
}

return $server->getScheduler()->peekLocalComplex($this);
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/PocketMine-TesterPlugin