Resource TaskChain - Proper Async Operations and more!

Discussion in 'Spigot Plugin Development' started by Aikar, Oct 30, 2016.

  1. Is it also able to make a aSync task spread over ex 10 seconds. because I want to send 51*51*51 packets to a player. and doing this with a new thread doesnt keep the tps high.
     
  2. The code that needs to get the value you desire also needs to be a task in your task chain.

    If you want callers to access it without directly using TaskChain, you can do something like:

    Code (Java):

    public void getSomeValue(SomeInputType someInput, TaskChainTasks.LastTask<SomeResponseType> desiredValueCallback) {
        Plugin.newChain()
            .asyncFirst(() -> {
                ...
                return someValue;
            })
            .syncLast(desiredValueCallback)
            .execute();
    }
    // then some where else
    SomeClass.getSomeValue(someInput, (someResponse) -> {
        // use someResponse
    });
     
     
  3. i went ahead and just used a completablefuture
     
  4. That's same style as TaskChain, just TC provides a more clear thread context switching API.
     
  5. Assuming this is regarding your xray simulation plugin, consider using the PacketPlayOutMapChunk instead of individual block updates.
     
  6. I tried it but it doesnt work. And no one helps me so yeah
     
  7. Take a look at how Orebfuscator handles fake chunk data. I'd look into it more, but I work a crapton and mobile is godawful for reading code on.
     
  8. Hey. I load my custom player object from a file. When the file gets big, it takes time to get the user. Therefore I need something like:

    Code (Java):
    /**
    * Get the object representing the player's data for this plugin
    *
    * @param id UUID of player
    * @return Object representing the player data
    */

    public static MyPlayer getPlayer(final UUID id) {
        BukkitTaskChainFactory.create(MyPlugin#getInstance())
                .newChain()
                .asyncFirst(() -> {
                    try {
                        return CacheManager.PlayersCache().get(id);
                    } catch (ExecutionException ignored) {} // Do nothing, handled with null
                    return null;
                })
                .abortIfNull()
                .syncLast((rPlayer) -> rPlayer.bukkitPlayer().sendMessage("Got you async? " + rPlayer.getId().toString()))
                .execute();

        return null;
    }
    When this method is used for example in a command:
    Code (Java):
    ... onCommand(...) {
      UUID id = Player#getUniqueId();
     
      // Then
      MyPlayer = ClassContainingStaticMethod#getPlayer(id);

      // Returns null, but then later sends a message that it got me async
    }
    So The above first says I'm null in a MyPlayer# == null check, then it sends me my message sync. That is because the result didn't come back yet. Do you have any way with TaskChain where I could wait for the result when checking for it, so this doesn't happen. I checked your examples, but could only find void methods. Thanks! Any code example providing this would be very helpful.
     
    #88 ExpDev, Jun 25, 2017
    Last edited: Jun 25, 2017
  9. MiniDigger

    Supporter

    just return the chain in getPlayer so that you can add more tasks to the chain and execute it later. something like this:
    Code (Text):
    public static TaskChain<MyPlayer> getPlayer(final UUID id) {
       return BukkitTaskChainFactory.create(MyPlugin#getInstance())
                .newChain()
                .asyncFirst(() -> {
                    try {
                        return CacheManager.PlayersCache().get(id);
                    } catch (ExecutionException ignored) {} // Do nothing, handled with null
                    return null;
                })
                .abortIfNull();
    }

     ClassContainingStaticMethod#getPlayer(id).syncLast((rPlayer)->doStuff with player).execute();
     
     
    • Informative Informative x 1
  10. I wouldn't put the abort if null there :p let the caller handle that so they can use appropriate messaging and such when needed.
     
  11. bump for chainage!
     
  12. How do you / would you handle the plugin disabling in the following scenario?

    * Plugin is enabled
    * Create and start execution of task chain = sync: do some work (ex. marking in some map that the data for a player is currently getting loaded), async: heavy task (ex. get data from db), sync: handle data (ex. add player data and mark as 'finished loading')
    * During the async task the plugin gets disabled

    From (quickly) looking into the source code (https://github.com/aikar/TaskChain/.../java/co/aikar/taskchain/TaskChain.java#L1098) you seem to execute the following sync task inside the non-main thread (the thread of the last task in the chain, in this case async task). Isn't this unsafe to do?

    And not executing the sync task at all, even if it fits into the 60 seconds timeout, might mean data being lost which is meant to be handled before shutdown, or a invalid state being left with for after re-enabling of the plugin (consider reloading implemented by disabling and then enabling again).
    And posting to main thread might be not possible, in case (like in bukkit) tasks cannot be scheduled while the plugin is disabled / currently disabling, and might also make no sense, because the task gets cancelled anyway right after the plugin finishes disabling.

    So how do you handle this in your plugins?
     
    • Like Like x 1
  13. No, because the main thread is blocked during this time, so nothing about its state can even change.

    It should all work just fine. in onDisable of your plugin, it will stop the async thread, and wait for all pending jobs to finish.

    Any chain that was in the middle of execution follow run its course to completion on the async thread (holding the onDisable until all chains are complete). This executes every task in the chain.

    60 seconds should be extremely more than necessary, as if you have so much going on that you can't complete it all in 60 seconds.... Then I'm not sure what else you could do other than extend timeout, but that's going to hang the server quite a long time in onDisable.

    The only risk I can see is if you use .delay() for long periods of time.
     
    • Informative Informative x 1
  14. But what about bukkit API calls which throw an error if called from a thread different to the main thread? Example, adding an entity to a world / loading a chunk will trigger an error if run from a non-main thread as far as I can remember. (at least if run on spigot)
     
    #94 blablubbabc, Aug 16, 2017
    Last edited: Aug 16, 2017
  15. Yes, that is a risk that could happen in undesirable situations, but that risk is no different than what we have today if you decide to do it in pure bukkit methods.

    However, it is a solvable problem. If the chain is currently async, and a shutdown has been detected, and the next task is sync, we can push it to a unfinished chains pool, and once the async pool has finished processing, then check the unfinished chains pool (which would only be filled if someone ran into this scenario), and then run the chain to completion in the onDisable event.
     
  16. [TaskChain:shutdown-safety] 1 new commit
    214c4d2
    Better handle shutting down factories to get sync tasks back on main - aikar

    Working on it here, I just need to put more time in concurrency safety, as I fear if the code checks isShuttingDown, returns false, THEN shutting down is set to true, AND the task is currently async, and then tries to impl.postToMain, , that postToMain might be lost.
    Need to test this change and think on it more.
     
    • Like Like x 1
  17. This looks really usefull to me but i never understood async operations xD
    RIP me then
     
  18. Great opportunity to learn :)
     
  19. foncused

    Junior Mod Patron

    After spending a bit of time implementing this resource into my core, I must say it proved itself incredibly useful. Massive thanks to @Aikar (and anyone else involved) for your time and effort on this one.

    One question: Should I ever see a TPS drop from running several DB operations on a single async task? I write some caches out on PlayerQuitEvent and noticed an occasional drop to ~19.98. I figured this would happen sync, but not in async.

    EDIT: Do you take donations? :)
     

Share This Page