Jump to content

Trance

Global Moderator
  • Posts

    3,880
  • Credits

  • Joined

  • Last visited

  • Days Won

    58
  • Feedback

    0%

Everything posted by Trance

  1. @injonesIs the design only being sold to one customer? Are you selling only the design or is the code included as well? Does the purchase include the PSD file?
  2. of course you can emulate but AAC is the issue if the server is using it.
  3. https://maxcheaters.com/forum/72-marketplace-l2packs-files/
  4. Mobius is using aCis' buffer as well, so it's mostly the same.
  5. In my previous comment, I was talking about a situation where you don't give someone the source code. Hypothetically speaking. Most people prefer using a finished product and don't like to help with its development. I also admit that I can be lazy when it comes to doing things that I find boring. But when it comes to aCis, developers enjoy using it, even if it's not yet complete. They like the way it's written and organized. I'm worried that by the time aCis is ready to use, fewer people will be interested in it. Right now, everyone wants to use the Classic or Essence client. You should think about making a new version of aCis that uses the Interlude data, so that people who like that version can still use it. This will attract even more people towards aCis.
  6. Those are pretty good numbers. Now imagine the struggle of server developers using aCis if they had no source and you would be the only contributor.
  7. Russian speakers in this community have a tendency to be sarcastic and egotistic. Does having hundreds of open tickets make a project good for you?
  8. You've deviated slightly from the main point, but yes, you're correct. What you're mentioning has always been true; "just for fun" servers have been around since L2J started. It's just that people didn't notice them much before.
  9. No L2J project meets the standards to be sold without its source code. It's unfair to make clients solely dependent on you when you can’t provide what you have promised.
  10. The integration of Google Captcha (or any other captcha) will be implemented within the website. By the way, if you're interested in developing an algorithm to monitor players' actions, this could serve as a starting point: P.S. You may incorporate a variation of +/- 10 points for the coordinates. package gold.lineage2.beta; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.Config; import gold.lineage2.commons.database.DatabaseFactory; /** * Validates and stores player coordinates in a database. * @author Trance */ public class CoordinateQueue { private static final Logger LOGGER = Logger.getLogger(CoordinateQueue.class.getName()); private final BlockingQueue<CoordinateRequest> queue = new LinkedBlockingQueue<>(); private final Thread validationThread; /** * Creates a new thread that continuously retrieves CoordinateRequest objects from a queue, * and then validates and stores the player coordinates contained in the request. * The validation process is performed by calling the validateAndStorePlayerCoordinates method. * If an InterruptedException is thrown during the validation, the thread logs a warning message and sets its interrupt status. */ public CoordinateQueue() { validationThread = new Thread(() -> { while (true) { try { CoordinateRequest request = queue.take(); validateAndStorePlayerCoordinates(request.getPlayerName(), request.getX(), request.getY(), request.getZ()); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Validation thread was interrupted", e); break; } } }); // A daemon thread is a background thread that is used to support the main program but doesn't prevent it from ending. validationThread.setDaemon(true); // Starting a new thread. validationThread.start(); } /** * Adds a new CoordinateRequest object to the queue with the provided playerName, x, y, and z values. * @param playerName * @param x * @param y * @param z */ public void addRequest(String playerName, int x, int y, int z) { queue.add(new CoordinateRequest(playerName, x, y, z)); } /** * Interrupts the validation thread to shut it down. */ public void shutdown() { validationThread.interrupt(); } /** * Stores the player's name and the three coordinates (x, y, z) */ private static class CoordinateRequest { private final String playerName; private final int x; private final int y; private final int z; public CoordinateRequest(String playerName, int x, int y, int z) { this.playerName = playerName; this.x = x; this.y = y; this.z = z; } public String getPlayerName() { return playerName; } public int getX() { return x; } public int getY() { return y; } public int getZ() { return z; } } /** * Records and validates the coordinate data of a player. * If there is a previous visit, the number of visits is incremented by 1. * If there isn't a previous visit, a new record is inserted into the database. * @param playerName * @param x * @param y * @param z */ private void validateAndStorePlayerCoordinates(String playerName, int x, int y, int z) { final int intervalMinutes = Config.VALIDATION_INTERVAL; final long intervalMilliseconds = intervalMinutes * 60 * 1000; final long currentTime = System.currentTimeMillis(); try (Connection con = DatabaseFactory.getConnection(); PreparedStatement insert = con.prepareStatement("INSERT INTO character_coordinate_history (playerName, x, y, z, visitTime, visitCount) VALUES (?, ?, ?, ?, ?, 1)"); PreparedStatement select = con.prepareStatement("SELECT visitTime, visitCount FROM character_coordinate_history WHERE playerName = ? AND x = ? AND y = ? AND z = ? AND visitTime >= ? ORDER BY visitTime DESC"); PreparedStatement update = con.prepareStatement("UPDATE character_coordinate_history SET visitCount = visitCount + 1 WHERE playerName = ? AND x = ? AND y = ? AND z = ? AND visitTime = ?")) { select.setString(1, playerName); select.setInt(2, x); select.setInt(3, y); select.setInt(4, z); select.setLong(5, currentTime - intervalMilliseconds); ResultSet resultSet = select.executeQuery(); if (resultSet.next()) { long visitTime = resultSet.getLong("visitTime"); int visitCount = resultSet.getInt("visitCount"); if (visitCount >= 2) { LOGGER.log(Level.INFO, "Player " + playerName + " visited the same coordinates " + x + " " + y + " " + z + " within the last " + intervalMinutes + " minutes."); return; } update.setString(1, playerName); update.setInt(2, x); update.setInt(3, y); update.setInt(4, z); update.setLong(5, visitTime); update.executeUpdate(); } else { insert.setString(1, playerName); insert.setInt(2, x); insert.setInt(3, y); insert.setInt(4, z); insert.setLong(5, currentTime); insert.executeUpdate(); } } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to validate and store player coordinates", e); } } } Index: java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java =================================================================== --- java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java (revision 9) +++ java/gold/lineage2/gameserver/network/clientpackets/ValidatePosition.java (working copy) @@ -16,8 +16,6 @@ */ package gold.lineage2.gameserver.network.clientpackets; +import gold.lineage2.beta.CoordinateQueue; import gold.lineage2.commons.network.PacketReader; import gold.lineage2.gameserver.data.xml.DoorData; import gold.lineage2.gameserver.model.World; @@ -116,13 +114,6 @@ player.setClientZ(_z); player.setClientHeading(_heading); // No real need to validate heading. + // Validates and stores player coordinates in a database. + final CoordinateQueue queue = new CoordinateQueue(); + ueue.addRequest(player.getName(), _x, _y, _z); + // Mobius: Check for possible door logout and move over exploit. Also checked at MoveBackwardToLocation. if (!DoorData.getInstance().checkIfDoorsBetween(realX, realY, realZ, _x, _y, _z, player.getInstanceWorld(), false)) { package gold.lineage2.beta; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.commons.database.DatabaseFactory; /** * @author Trance */ public class ActionQueue { private static final Logger LOGGER = Logger.getLogger(ActionQueue.class.getName()); /** * Checks the timing difference between consecutive player actions represented as an array of timestamps. * @param playerName * @param actionName * @param actionTimestamps * @param threshold */ public void checkActionTiming(String playerName, String actionName, long[] actionTimestamps, long threshold) { for (int i = 1; i < actionTimestamps.length; i++) { long timeDiff = actionTimestamps[i] - actionTimestamps[i - 1]; if (timeDiff < threshold) { recordActionToDatabase(playerName, actionName, actionTimestamps[i], timeDiff); } } } /** * Records a player's action timestamp and time difference. * @param playerName * @param actionName * @param actionTimestamp * @param timeDiff */ private void recordActionToDatabase(String playerName, String actionName, long actionTimestamp, long timeDiff) { final String insert = "INSERT INTO character_actions_history (timestamp, time_diff, count, action_name, player_name) " + "SELECT ?, ?, IFNULL(MAX(count) + 1, 1), ?, ? FROM character_actions_history WHERE timestamp < ?"; final String update = "UPDATE character_actions_history SET time_diff = ?, count = ? WHERE id = ?"; try (Connection con = DatabaseFactory.getConnection(); PreparedStatement insertStmt = con.prepareStatement(insert); PreparedStatement updateStmt = con.prepareStatement(update)) { // Set the parameter values for the INSERT statement insertStmt.setLong(1, actionTimestamp); insertStmt.setLong(2, timeDiff); insertStmt.setString(3, actionName); insertStmt.setString(4, playerName); insertStmt.setLong(5, actionTimestamp); // Execute the INSERT statement and get the count value int count = insertStmt.executeUpdate(); // Set the parameter values for the UPDATE statement updateStmt.setLong(1, timeDiff); updateStmt.setInt(2, count); updateStmt.setInt(3, count > 1 ? count - 1 : 1); updateStmt.executeUpdate(); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to record action to database", e); } } } package gold.lineage2.beta; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Level; import java.util.logging.Logger; import gold.lineage2.commons.database.DatabaseFactory; /** * Checking the integrity of the database tables. * @author Trance */ public class DatabaseIntegrity { private static final Logger LOGGER = Logger.getLogger(DatabaseIntegrity.class.getName()); protected DatabaseIntegrity() { createCharacterCoordinateHistoryTable(); createCharacterActionsHistoryTable(); } /** * Creates a table called character_coordinate_history in a database. */ private static void createCharacterCoordinateHistoryTable() { final String tableName = "character_coordinate_history"; final String columns = "playerName VARCHAR(100), x INT, y INT, z INT, visitTime BIGINT, visitCount INT DEFAULT 0"; final String primaryKey = "playerName, x, y, z, visitTime"; final String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + columns + ", PRIMARY KEY (" + primaryKey + "))"; try (Connection con = DatabaseFactory.getConnection(); Statement stmt = con.createStatement()) { stmt.executeUpdate(sql); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to create table " + tableName, e); } } /** * Creates a table called character_actions_history in a database. */ private void createCharacterActionsHistoryTable() { final String tableName = "character_actions_history"; final String columnTimestamp = "timestamp"; final String columnTimeDiff = "time_diff"; final String columnCount = "count"; final String columnPlayerName = "player_name"; final String primaryKey = "id"; final String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + primaryKey + " INT AUTO_INCREMENT PRIMARY KEY, " + columnTimestamp + " BIGINT, " + columnTimeDiff + " BIGINT, " + columnCount + " INT NOT NULL DEFAULT 1, " + columnPlayerName + " VARCHAR(255)) ENGINE=InnoDB"; try (Connection con = DatabaseFactory.getConnection(); Statement stmt = con.createStatement()) { stmt.executeUpdate(sql); } catch (SQLException e) { LOGGER.log(Level.WARNING, "Failed to create table " + tableName, e); } } public static DatabaseIntegrity getInstance() { return SingletonHolder.INSTANCE; } private static class SingletonHolder { protected static final DatabaseIntegrity INSTANCE = new DatabaseIntegrity(); } }
  11. What if you introduce a Google Captcha verification system that would be better against Adrenaline. Game client opens server's website. On the server's website, the player would be prompted to complete a Google Captcha challenge. Once the player successfully completes the Captcha, the server would receive a confirmation and send an authorization back to the game server, allowing the player to continue playing without any disruption.
  12. Hey everyone, I want to talk about an important issue concerning L2J projects. It's time for us to stop supporting projects that are sold compiled without source code, and also to discourage others from buying or participating in them. Let me explain why. Currently, there are no L2J projects out there that are stable or of high enough quality to justify being sold without providing the source code. If you want to sell a project like that, you should first make sure it meets the highest standards. The sad truth is that most active L2J projects today don't have enough developers or a truly active development team to keep up with the demands of their users. You either need to provide a near-perfect project with everything implemented, working as expected, and free of bugs, or you need a larger, active development team to achieve that. Using an L2J project without access to the source code is a dead-end. Since there are no projects that meet the criteria I mentioned above, you're better off steering clear of them. In conclusion, do not buy an L2J project without a source.
  13. @Mobiusis one of the most active and well-respected developers in the L2J community, known for making significant contributions to both private and public projects. His biggest strength is in his ability to support a wide range of Chronicles. It's no wonder that Mobius and his work are considered to be at the forefront of the best L2J projects available. And let's be honest, with so many Chronicles supported, it's hard to argue with this. If you have experience with L2J, L2JServer is a good option with a solid foundation to build on. Or to put it differently, if you have enough time to spare and are willing to learn. If you want to quickly set up a custom PvP server with many customs and don't want to spend a lot of time on development or worry about critical bugs; in theory, @`NeverMore's L2JSunrise may be the best choice. It's been around for a long time, so you probably won't run into any major problems. However, I don't have any technical details, as I only worked with it once for a client many years ago.
  14. It is closing the connection automatically because the Connection object is being created within the try block.
  15. It's unusual to have connection issues on localhost. When this happens, check your OS' RAM and CPU usage. If they're too high, it might cause timeouts. Also, look at your MySQL settings, especially max_allowed_packet and wait_timeout. If they're too low, increase them. Have you changed any settings before? Poorly written queries can also cause connection issues. You can perform a Java Flight Recording: https://maxcheaters.com/topic/247610-jdk-mission-control-performance-analysis-tool/
  16. Get ready for an exhilarating, action-packed journey in L2Gold Essence, a gold-style Lineage 2 server completely revamped on the Essence Client. We've crafted this low-rate server with official files and a wealth of engaging features, ensuring an unforgettable experience for all players: - Long-lasting buffs from the NPC Buffer to keep you empowered and ready for action - Auto-Hunting mode, ensuring a more relaxed, enjoyable gaming experience for everyone - Character rebirth up to 3 times, allowing you to retain your S-grade gear and experience faster, more satisfying progression - And much more Embrace the nostalgia with our completely reimagined Interlude map, featuring Interlude-like classes and skills, sprinkled with gold-style enhancements to improve underused classes. Expect additional high-level buffs for buffers and new skills for summoning classes, empowering you to dominate in PvP encounters. Our built-in UI Gatekeeper will teleport you to familiar zones from our first season, while introducing a breath of fresh air with fewer NPCs in town. L2Gold Essence artfully balances fond memories with exciting new elements, setting the stage for countless epic moments to come. But that's not all! Immerse yourself in a myriad of entertaining activities as you level up with higher gear, ensuring there's never a dull moment in the world of L2Gold Essence. From challenging dungeons to exhilarating PvP battles, you'll find endless opportunities to showcase your skills and make a name for yourself. As soon as we have certainty, we will provide additional details regarding the beta phase and/or launch date. Join our vibrant community on Discord and share your most memorable moments with fellow adventurers. Together, we'll forge new friendships, conquer epic challenges, and create lasting memories in the captivating world of L2Gold Essence. https://discord.gg/zA5KWH8cMc
  17. That is not normal. However, you can increase connection timeout to 60 seconds for testing purposes. jdbc:mariadb://127.0.0.1:3306/SERVER_DB?connectTimeout=60000?user=hidden&password=hidden All you can do is wait for the logging results.
  18. Temporarily stop them: sudo systemctl stop firewalld sudo systemctl stop iptables Check if localhost can reach MySQL on port 3306: mysql -u root -h localhost -p<your password> -e "SELECT 'Connected Successfully'"
  19. Ensure that the following information in your server properties is accurate: the database URL, login credentials, and password. If you are using Linux, it's a good idea to verify if any firewall rules might be blocking the loopback interface (lo) for localhost, as this could be the cause of the issue.
  20. You mentioned that you've fixed the issues, but how it would be ready for a live server when only a few fixes have been shared. It would be helpful and appreciated if you could share more information about the fixes or the process. It's always great when everyone can collaborate and learn from each other. If you choose not to share this information, it is unreasonable to expect others to. I, along with @Tryskell(I believe), am completely fed up with the arrogance of these so-called "developers." They perform half-baked fixes on critical issues and have the audacity to call it a major accomplishment. You all deserve your fate!
  21. Most L2 developers are not willing to work remotely through Team Viewer or AnyDesk due to the low income and their moody nature. Therefore, it is highly unlikely to find an L2 developer willing to work under these conditions.
  22. You don't need to copy and paste everything. Instead, you can use the same logic to implement it in your Thread Pool Manager. However, I am providing you with the files: https://files.lineage2.gold/threads.zip
  23. Hi, This is a custom implementation of a thread pool management system, designed to efficiently manage the execution of tasks in a multi-threaded environment. Automatically adjusts the size of the thread pools based on the system's CPU load and the number of available cores. This dynamic adjustment ensures that the thread pools are always optimized for the current system load, resulting in efficient resource utilization and improved performance. If the calculated sizes are equal to the current core pool sizes, this means that there's no need to update the pool sizes, as they are already optimized for the current system load. I haven't conducted extensive testing yet; however, the code can be thoroughly tested, and if any issues are encountered, I can make the necessary modifications. Comments were added throughout the code. For testing purposes you can do the following: scheduleAtFixedRate(() -> { try { purge(); updateThreadPoolSizes(); } catch (Exception e) { LOGGER.severe("Error during scheduled ThreadPool update: " + e.getMessage()); } }, 10, 10, TimeUnit.SECONDS); private static void updateThreadPoolSizes() { double systemLoad = getSystemCpuLoad(); int availableCores = Runtime.getRuntime().availableProcessors(); int newScheduledThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, true); int newInstantThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, false); if (newScheduledThreadPoolSize != scheduledExecutor.getCorePoolSize()) { scheduledExecutor.setCorePoolSize(newScheduledThreadPoolSize); LOGGER.info("Updated scheduled thread pool size to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } else { LOGGER.info("Scheduled thread pool size remains to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } if (newInstantThreadPoolSize != instantExecutor.getCorePoolSize()) { instantExecutor.setCorePoolSize(newInstantThreadPoolSize); LOGGER.info("Updated instant thread pool size to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } else { LOGGER.info("Instant thread pool size remains to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } } P.S.: This is the lowest it can get: return Math.max(isScheduledPool ? 16 : 8, threadPoolSize); The initial code: * This file is part of the L2Gold Classic project. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package gold.lineage2.commons.threads; import java.lang.management.ManagementFactory; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import com.sun.management.OperatingSystemMXBean; /** * A custom implementation of a thread pool management system, designed to efficiently manage the execution of tasks in a multi-threaded environment. * Automatically adjusts the size of the thread pools based on the system's CPU load and the number of available cores. This dynamic adjustment ensures * that the thread pools are always optimized for the current system load, resulting in efficient resource utilization and improved performance. * @author Trance */ public class ThreadPool { // Logger to log information and errors. private static final Logger LOGGER = Logger.getLogger(ThreadPool.class.getName()); // Maximum delay to be used to validate the delay. private static final long MAX_DELAY = TimeUnit.NANOSECONDS.toMillis(Long.MAX_VALUE - System.nanoTime()) / 2; // ScheduledThreadPoolExecutor for scheduled tasks. private static ScheduledThreadPoolExecutor scheduledExecutor; // ThreadPoolExecutor for instant tasks. private static ThreadPoolExecutor instantExecutor; /** * Initialize the ThreadPool with the appropriate sizes. */ public static void init() { LOGGER.info("ThreadPool: Initialized"); int availableCores = Runtime.getRuntime().availableProcessors(); int scheduledThreadPoolSize = availableCores * 4; int instantThreadPoolSize = availableCores * 2; scheduledExecutor = new ScheduledThreadPoolExecutor(scheduledThreadPoolSize, new PriorityThreadFactory("ScheduledThreadPool", Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy()); scheduledExecutor.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); scheduledExecutor.prestartAllCoreThreads(); LOGGER.info("Scheduled thread pool size: " + scheduledThreadPoolSize); instantExecutor = new ThreadPoolExecutor(instantThreadPoolSize, Integer.MAX_VALUE, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory("ThreadPoolExecutor", Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy()); instantExecutor.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl()); instantExecutor.prestartAllCoreThreads(); LOGGER.info("Instant thread pool size: " + instantThreadPoolSize); scheduleAtFixedRate(() -> { try { purge(); updateThreadPoolSizes(); } catch (Exception e) { LOGGER.severe("Error during scheduled ThreadPool update: " + e.getMessage()); } }, 5, 5, TimeUnit.MINUTES); } /** * Purge both thread pools. */ public static void purge() { scheduledExecutor.purge(); instantExecutor.purge(); } /** * Validate the delay ensuring it's within acceptable bounds. * @param delay The delay value to be validated. * @param timeUnit The time unit of the delay. * @return A validated delay value. */ private static long validate(long delay, TimeUnit timeUnit) { long delayInMilliseconds = timeUnit.toMillis(delay); long validatedDelay = Math.max(0, Math.min(MAX_DELAY, delayInMilliseconds)); if (delayInMilliseconds > validatedDelay) { return -1; } return timeUnit.convert(delayInMilliseconds, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable with a specific delay. * @param r The Runnable to be scheduled. * @param delay The delay before the Runnable is executed, in milliseconds. * @return A ScheduledFuture representing the pending result of the task. */ public static ScheduledFuture<?> schedule(Runnable r, long delay) { return schedule(r, delay, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable with a specific delay and TimeUnit. * @param r The Runnable to be scheduled. * @param delay The delay before the Runnable is executed. * @param timeUnit The time unit of the delay. * @return A ScheduledFuture representing the result of the scheduling. */ private static ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit timeUnit) { delay = validate(delay, timeUnit); if (delay == -1) { return null; } return scheduledExecutor.schedule(new RunnableWrapper(r), delay, timeUnit); } /** * Schedule a Runnable at a fixed rate with an initial delay. * @param r The Runnable to be scheduled. * @param initial The initial delay before the Runnable is executed for the first time. * @param delay The delay between the execution of the Runnable in subsequent runs. * @return A ScheduledFuture representing the result of the scheduling. */ public static ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long initial, long delay) { return scheduleAtFixedRate(r, initial, delay, TimeUnit.MILLISECONDS); } /** * Schedule a Runnable at a fixed rate with an initial delay and TimeUnit. * @param r The Runnable to be scheduled. * @param initial The initial delay before the Runnable is executed for the first time. * @param delay The delay between the execution of the Runnable in subsequent runs. * @param timeUnit The time unit of the delay. * @return A ScheduledFuture representing the result of the scheduling. */ private static ScheduledFuture<?> scheduleAtFixedRate(Runnable r, long initial, long delay, TimeUnit timeUnit) { initial = validate(initial, timeUnit); if (initial == -1) { return null; } delay = validate(delay, timeUnit); if (delay == -1) { return scheduledExecutor.schedule(new RunnableWrapper(r), initial, timeUnit); } return scheduledExecutor.scheduleAtFixedRate(new RunnableWrapper(r), initial, delay, timeUnit); } /** * Execute a Runnable instantly. * @param r The Runnable to be executed. */ public static void execute(Runnable r) { instantExecutor.execute(r); } /** * Shut down both thread pools. * @throws InterruptedException If the shutdown process is interrupted. */ public static void shutdown() throws InterruptedException { try { scheduledExecutor.shutdown(); if (!scheduledExecutor.awaitTermination(10, TimeUnit.SECONDS)) { scheduledExecutor.shutdownNow(); } } finally { instantExecutor.shutdown(); if (!instantExecutor.awaitTermination(1, TimeUnit.MINUTES)) { instantExecutor.shutdownNow(); } } } /** * Update the thread pool sizes based on system load. */ private static void updateThreadPoolSizes() { double systemLoad = getSystemCpuLoad(); int availableCores = Runtime.getRuntime().availableProcessors(); int newScheduledThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, true); int newInstantThreadPoolSize = calculateThreadPoolSize(availableCores, systemLoad, false); if (newScheduledThreadPoolSize != scheduledExecutor.getCorePoolSize()) { scheduledExecutor.setCorePoolSize(newScheduledThreadPoolSize); LOGGER.info("Updated scheduled thread pool size to " + newScheduledThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } if (newInstantThreadPoolSize != instantExecutor.getCorePoolSize()) { instantExecutor.setCorePoolSize(newInstantThreadPoolSize); LOGGER.info("Updated instant thread pool size to " + newInstantThreadPoolSize + ", CPU: " + getSystemCpuLoadPercentage()); } } /** * Calculate the thread pool size based on available cores, system load, and whether it's a scheduled pool. * @param availableCores The number of available processor cores. * @param systemLoad The current system load. * @param isScheduledPool A boolean indicating if the pool is a scheduled thread pool or not. * @return The calculated thread pool size. */ private static int calculateThreadPoolSize(int availableCores, double systemLoad, boolean isScheduledPool) { double factor; if (systemLoad <= 0.4) { factor = isScheduledPool ? 4 : 2; } else if (systemLoad <= 0.6) { factor = isScheduledPool ? 3 : 1.5; } else if (systemLoad <= 0.8) { factor = isScheduledPool ? 2 : 1; } else { factor = 0.5; } int threadPoolSize = (int) Math.round(availableCores * factor); return Math.max(isScheduledPool ? 16 : 8, threadPoolSize); } /** * Get the system CPU load. * @return A double value representing the system CPU load. */ @SuppressWarnings("deprecation") private static double getSystemCpuLoad() { OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); return osBean.getSystemCpuLoad(); } /** * Get the system CPU load as a percentage. * @return A string representing the system CPU load percentage. */ private static String getSystemCpuLoadPercentage() { double cpuLoad = getSystemCpuLoad(); return String.format("%.2f%%", cpuLoad * 100); } }
×
×
  • Create New...