Skip to content

Commit

Permalink
Placeholders - Add timeout for placeholders to return value, causing …
Browse files Browse the repository at this point in the history
…TAB to freeze
  • Loading branch information
NEZNAMY committed Dec 23, 2022
1 parent 27fe9df commit 37caed3
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 18 deletions.
15 changes: 15 additions & 0 deletions api/src/main/java/me/neznamy/tab/api/TabConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,23 @@ public static class Placeholder {
public static final String VAULT_SUFFIX = "%vault-suffix%";
public static final String HEALTH = "%health%";

/**
* Minimum refresh interval of a placeholder in milliseconds, used to set refresh interval of the main task
*/
public static final int MINIMUM_REFRESH_INTERVAL = 50;

/**
* Timeout in milliseconds for a placeholder to return value
* to prevent SQL tasks by PlaceholderAPI placeholders from freezing TAB
*/
public static final int PLACEHOLDER_RETRIEVE_TIMEOUT = 50;

/**
* Internal constant used to detect if placeholder threw an error.
* If so, placeholder's last known value is displayed instead.
*/
public static final String ERROR_VALUE = "ERROR";


public static String condition(String name) {
return "%condition:" + name + "%";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
package me.neznamy.tab.shared.placeholders;

import java.util.*;
import java.util.function.Function;

import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.TAB;

import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;

/**
* Implementation of the PlayerPlaceholder interface
*/
public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlaceholder {

/**
* Internal constant used to detect if placeholder threw an error.
* If so, placeholder's last known value is displayed.
*/
private final String ERROR_VALUE = "ERROR";

/** Placeholder function returning fresh output on request */
private final Function<TabPlayer, Object> function;

Expand Down Expand Up @@ -59,8 +57,8 @@ public boolean update(TabPlayer p) {
if (identifier.equals(newValue) && !lastValues.containsKey(p)) {
lastValues.put(p, identifier);
}
if (!lastValues.containsKey(p) || (!ERROR_VALUE.equals(newValue) && !identifier.equals(newValue) && !lastValues.get(p).equals(newValue))) {
lastValues.put(p, ERROR_VALUE.equals(newValue) ? identifier : newValue);
if (!lastValues.containsKey(p) || (!TabConstants.Placeholder.ERROR_VALUE.equals(newValue) && !identifier.equals(newValue) && !lastValues.get(p).equals(newValue))) {
lastValues.put(p, TabConstants.Placeholder.ERROR_VALUE.equals(newValue) ? identifier : newValue);
updateParents(p);
if (TAB.getInstance().getPlaceholderManager().getTabExpansion() != null)
TAB.getInstance().getPlaceholderManager().getTabExpansion().setPlaceholderValue(p, identifier, newValue);
Expand Down Expand Up @@ -127,10 +125,14 @@ public String getLastValue(TabPlayer p) {
@Override
public Object request(TabPlayer p) {
try {
return function.apply(p);
return placeholderFutureThreadPool.submit(() -> function.apply(p)).get(
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
TAB.getInstance().getErrorManager().criticalError("Placeholder " + identifier + " took longer than " +
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT + "ms to retrieve value for player " + p.getName() + ", aborting.", null);
} catch (Throwable t) {
TAB.getInstance().getErrorManager().placeholderError("Player placeholder " + identifier + " generated an error when setting for player " + p.getName(), t);
return ERROR_VALUE;
}
return TabConstants.Placeholder.ERROR_VALUE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;

import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
Expand Down Expand Up @@ -116,10 +119,14 @@ public void updateFromNested(TabPlayer player) {
@Override
public Object request(TabPlayer viewer, TabPlayer target) {
try {
return function.apply(viewer, target);
return placeholderFutureThreadPool.submit(() -> function.apply(viewer, target)).get(
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
TAB.getInstance().getErrorManager().criticalError("Placeholder " + identifier + " took longer than " +
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT + "ms to retrieve value for viewer " + viewer.getName() + " and target " + target.getName() + ", aborting.", null);
} catch (Throwable t) {
TAB.getInstance().getErrorManager().placeholderError("Relational placeholder " + identifier + " generated an error when setting for players " + viewer.getName() + " and " + target.getName(), t);
return "ERROR";
}
return TabConstants.Placeholder.ERROR_VALUE;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package me.neznamy.tab.shared.placeholders;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.ServerPlaceholder;
Expand Down Expand Up @@ -50,7 +53,7 @@ public boolean update() {
if (identifier.equals(newValue) && lastValue == null) {
lastValue = identifier;
}
if (!"ERROR".equals(newValue) && !identifier.equals(newValue) && (lastValue == null || !lastValue.equals(newValue))) {
if (!TabConstants.Placeholder.ERROR_VALUE.equals(newValue) && !identifier.equals(newValue) && (lastValue == null || !lastValue.equals(newValue))) {
lastValue = newValue;
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
updateParents(player);
Expand Down Expand Up @@ -113,10 +116,14 @@ public String getLastValue() {
@Override
public Object request() {
try {
return supplier.get();
return placeholderFutureThreadPool.submit(supplier::get).get(
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
TAB.getInstance().getErrorManager().criticalError("Placeholder " + identifier + " took longer than " +
TabConstants.Placeholder.PLACEHOLDER_RETRIEVE_TIMEOUT + "ms to retrieve value, aborting.", null);
} catch (Throwable t) {
TAB.getInstance().getErrorManager().placeholderError("Server placeholder " + identifier + " generated an error", t);
return "ERROR";
}
return TabConstants.Placeholder.ERROR_VALUE;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.placeholders;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
Expand All @@ -8,12 +9,17 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* General collection of variables and functions shared between all placeholder types
*/
public abstract class TabPlaceholder implements Placeholder {

/** Thread pool for submitting placeholder requests with a timeout */
protected static ExecutorService placeholderFutureThreadPool = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("TAB Placeholder retrieving thread %d").build());

/** Refresh interval of the placeholder */
private final int refresh;

Expand Down

0 comments on commit 37caed3

Please sign in to comment.