2013-09-29 20:16:47 +00:00
package com.imaginarycode.minecraft.redisbungee ;
2016-02-27 04:17:24 +00:00
import com.google.common.cache.Cache ;
import com.google.common.cache.CacheBuilder ;
2014-05-23 03:37:26 +00:00
import com.google.common.collect.* ;
2013-11-14 00:28:53 +00:00
import com.google.common.io.ByteStreams ;
2014-04-20 05:12:28 +00:00
import com.google.gson.Gson ;
2014-03-31 15:19:33 +00:00
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent ;
2015-06-22 07:39:26 +00:00
import com.imaginarycode.minecraft.redisbungee.util.* ;
2016-03-08 01:56:58 +00:00
import com.imaginarycode.minecraft.redisbungee.util.uuid.NameFetcher ;
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDFetcher ;
import com.imaginarycode.minecraft.redisbungee.util.uuid.UUIDTranslator ;
2015-03-28 20:46:08 +00:00
import com.squareup.okhttp.Dispatcher ;
2015-01-25 05:17:52 +00:00
import com.squareup.okhttp.OkHttpClient ;
2015-04-18 14:13:02 +00:00
import lombok.AccessLevel ;
2014-04-19 20:08:49 +00:00
import lombok.Getter ;
2015-06-24 09:54:19 +00:00
import lombok.NoArgsConstructor ;
2014-01-02 05:14:58 +00:00
import lombok.NonNull ;
2015-09-27 15:49:03 +00:00
import net.md_5.bungee.api.ProxyServer ;
2013-09-29 20:16:47 +00:00
import net.md_5.bungee.api.connection.ProxiedPlayer ;
import net.md_5.bungee.api.plugin.Plugin ;
2013-12-24 04:12:54 +00:00
import net.md_5.bungee.config.Configuration ;
import net.md_5.bungee.config.ConfigurationProvider ;
import net.md_5.bungee.config.YamlConfiguration ;
2015-09-27 15:49:03 +00:00
import redis.clients.jedis.* ;
2013-12-04 21:54:51 +00:00
import redis.clients.jedis.exceptions.JedisConnectionException ;
2013-09-29 20:16:47 +00:00
import java.io.* ;
2016-06-26 08:19:10 +00:00
import java.lang.reflect.Field ;
2014-04-20 05:12:28 +00:00
import java.util.* ;
2016-06-26 08:19:10 +00:00
import java.util.concurrent.* ;
2014-06-06 21:38:03 +00:00
import java.util.concurrent.atomic.AtomicInteger ;
2013-12-04 21:54:51 +00:00
import java.util.logging.Level ;
2013-09-29 20:16:47 +00:00
2014-02-21 23:49:26 +00:00
import static com.google.common.base.Preconditions.checkArgument ;
2014-01-02 05:14:58 +00:00
2013-11-15 22:05:29 +00:00
/ * *
* The RedisBungee plugin .
2014-03-30 04:31:35 +00:00
* < p >
2013-11-15 22:05:29 +00:00
* The only function of interest is { @link # getApi ( ) } , which exposes some functions in this class .
* /
2014-04-19 20:08:49 +00:00
public final class RedisBungee extends Plugin {
@Getter
2014-09-21 17:56:46 +00:00
private static Gson gson = new Gson ( ) ;
private static RedisBungeeAPI api ;
2015-04-18 14:13:02 +00:00
@Getter ( AccessLevel . PACKAGE )
2014-09-21 17:56:46 +00:00
private static PubSubListener psl = null ;
@Getter
2014-04-19 20:08:49 +00:00
private JedisPool pool ;
2014-04-20 05:12:28 +00:00
@Getter
private UUIDTranslator uuidTranslator ;
2015-04-18 14:13:02 +00:00
@Getter ( AccessLevel . PACKAGE )
private static RedisBungeeConfiguration configuration ;
2014-08-10 03:16:47 +00:00
@Getter
private DataManager dataManager ;
2014-09-21 17:55:14 +00:00
@Getter
2015-01-25 05:17:52 +00:00
private static OkHttpClient httpClient ;
2015-12-06 07:44:21 +00:00
private volatile List < String > serverIds ;
2015-06-24 10:06:34 +00:00
private final AtomicInteger nagAboutServers = new AtomicInteger ( ) ;
2016-01-18 23:01:28 +00:00
private final AtomicInteger globalPlayerCount = new AtomicInteger ( ) ;
2016-06-28 20:39:05 +00:00
private Future < ? > integrityCheck ;
private Future < ? > heartbeatTask ;
2015-06-21 21:32:28 +00:00
private boolean usingLua ;
private LuaManager . Script serverToPlayersScript ;
2015-12-06 21:31:21 +00:00
private LuaManager . Script getPlayerCountScript ;
2016-01-10 17:47:49 +00:00
private static final Object SERVER_TO_PLAYERS_KEY = new Object ( ) ;
2016-02-27 04:17:24 +00:00
private final Cache < Object , Multimap < String , UUID > > serverToPlayersCache = CacheBuilder . newBuilder ( )
. expireAfterWrite ( 5 , TimeUnit . SECONDS )
. build ( ) ;
2013-10-15 00:40:32 +00:00
2013-10-13 19:44:20 +00:00
/ * *
2013-11-15 22:05:29 +00:00
* Fetch the { @link RedisBungeeAPI } object created on plugin start .
2013-10-13 19:44:20 +00:00
*
2013-11-15 22:05:29 +00:00
* @return the { @link RedisBungeeAPI } object
2013-10-13 19:44:20 +00:00
* /
2013-11-15 22:05:29 +00:00
public static RedisBungeeAPI getApi ( ) {
return api ;
}
2014-09-21 17:56:46 +00:00
static PubSubListener getPubSubListener ( ) {
return psl ;
}
2014-04-04 03:03:27 +00:00
final List < String > getServerIds ( ) {
2014-04-19 19:54:30 +00:00
return serverIds ;
}
2015-11-15 16:01:54 +00:00
private List < String > getCurrentServerIds ( boolean nag , boolean lagged ) {
2015-06-06 21:30:45 +00:00
try ( Jedis jedis = pool . getResource ( ) ) {
2015-11-15 16:01:54 +00:00
long time = getRedisTime ( jedis . time ( ) ) ;
int nagTime = 0 ;
if ( nag ) {
nagTime = nagAboutServers . decrementAndGet ( ) ;
if ( nagTime < = 0 ) {
nagAboutServers . set ( 10 ) ;
}
2014-06-06 21:38:03 +00:00
}
2014-04-19 19:52:15 +00:00
ImmutableList . Builder < String > servers = ImmutableList . builder ( ) ;
Map < String , String > heartbeats = jedis . hgetAll ( " heartbeats " ) ;
for ( Map . Entry < String , String > entry : heartbeats . entrySet ( ) ) {
try {
2015-01-25 05:04:34 +00:00
long stamp = Long . parseLong ( entry . getValue ( ) ) ;
2015-11-15 16:01:54 +00:00
if ( lagged ? time > = stamp + 30 : time < = stamp + 30 )
2014-04-19 19:52:15 +00:00
servers . add ( entry . getKey ( ) ) ;
2015-11-15 16:01:54 +00:00
else if ( nag & & nagTime < = 0 ) {
2021-04-27 15:54:34 +00:00
getLogger ( ) . severe ( entry . getKey ( ) + " is " + ( time - stamp ) + " seconds behind! (Time not synchronized or server down?) and was removed from heartbeat. " ) ;
2021-04-27 15:49:58 +00:00
jedis . hdel ( " heartbeats " , entry . getKey ( ) ) ;
2014-06-06 21:38:03 +00:00
}
2014-04-19 19:52:15 +00:00
} catch ( NumberFormatException ignored ) {
}
}
return servers . build ( ) ;
2014-04-04 03:03:27 +00:00
} catch ( JedisConnectionException e ) {
2015-11-15 16:01:54 +00:00
getLogger ( ) . log ( Level . SEVERE , " Unable to fetch server IDs " , e ) ;
2015-04-18 14:13:02 +00:00
return Collections . singletonList ( configuration . getServerId ( ) ) ;
2014-04-04 03:03:27 +00:00
}
2014-01-26 00:06:33 +00:00
}
2015-06-22 10:06:02 +00:00
public Set < UUID > getPlayersOnProxy ( String server ) {
checkArgument ( getServerIds ( ) . contains ( server ) , server + " is not a valid proxy ID " ) ;
try ( Jedis jedis = pool . getResource ( ) ) {
Set < String > users = jedis . smembers ( " proxy: " + server + " :usersOnline " ) ;
ImmutableSet . Builder < UUID > builder = ImmutableSet . builder ( ) ;
for ( String user : users ) {
builder . add ( UUID . fromString ( user ) ) ;
}
return builder . build ( ) ;
}
}
2014-04-20 05:12:28 +00:00
final Multimap < String , UUID > serversToPlayers ( ) {
2016-01-10 17:47:49 +00:00
try {
return serverToPlayersCache . get ( SERVER_TO_PLAYERS_KEY , new Callable < Multimap < String , UUID > > ( ) {
@Override
public Multimap < String , UUID > call ( ) throws Exception {
Collection < String > data = ( Collection < String > ) serverToPlayersScript . eval ( ImmutableList . < String > of ( ) , getServerIds ( ) ) ;
ImmutableMultimap . Builder < String , UUID > builder = ImmutableMultimap . builder ( ) ;
String key = null ;
for ( String s : data ) {
if ( key = = null ) {
key = s ;
continue ;
}
2015-06-21 21:32:28 +00:00
2016-01-10 17:47:49 +00:00
builder . put ( key , UUID . fromString ( s ) ) ;
key = null ;
}
2015-12-06 21:31:21 +00:00
2016-01-10 17:47:49 +00:00
return builder . build ( ) ;
}
} ) ;
} catch ( ExecutionException e ) {
throw new RuntimeException ( e ) ;
}
2013-12-27 20:40:58 +00:00
}
2013-12-24 05:15:06 +00:00
final int getCount ( ) {
2016-01-18 23:01:28 +00:00
return globalPlayerCount . get ( ) ;
}
final int getCurrentCount ( ) {
2015-12-06 21:31:21 +00:00
Long count = ( Long ) getPlayerCountScript . eval ( ImmutableList . < String > of ( ) , ImmutableList . < String > of ( ) ) ;
return count . intValue ( ) ;
2013-10-15 00:40:32 +00:00
}
2015-06-24 11:04:01 +00:00
private Set < String > getLocalPlayersAsUuidStrings ( ) {
ImmutableSet . Builder < String > builder = ImmutableSet . builder ( ) ;
for ( ProxiedPlayer player : getProxy ( ) . getPlayers ( ) ) {
builder . add ( player . getUniqueId ( ) . toString ( ) ) ;
}
return builder . build ( ) ;
2014-01-20 15:34:40 +00:00
}
2014-04-20 05:12:28 +00:00
final Set < UUID > getPlayers ( ) {
2015-04-18 14:13:02 +00:00
ImmutableSet . Builder < UUID > setBuilder = ImmutableSet . builder ( ) ;
2013-09-29 20:16:47 +00:00
if ( pool ! = null ) {
2015-06-24 10:06:34 +00:00
try ( Jedis rsc = pool . getResource ( ) ) {
2014-04-26 23:43:09 +00:00
List < String > keys = new ArrayList < > ( ) ;
2014-04-19 19:54:30 +00:00
for ( String i : getServerIds ( ) ) {
2014-08-09 19:32:12 +00:00
keys . add ( " proxy: " + i + " :usersOnline " ) ;
2014-04-26 23:43:09 +00:00
}
2014-04-27 00:09:53 +00:00
if ( ! keys . isEmpty ( ) ) {
Set < String > users = rsc . sunion ( keys . toArray ( new String [ keys . size ( ) ] ) ) ;
if ( users ! = null & & ! users . isEmpty ( ) ) {
for ( String user : users ) {
2014-05-23 03:37:26 +00:00
try {
2014-04-27 00:09:53 +00:00
setBuilder = setBuilder . add ( UUID . fromString ( user ) ) ;
2014-05-23 03:37:26 +00:00
} catch ( IllegalArgumentException ignored ) {
2014-04-27 00:09:53 +00:00
}
2014-04-20 05:12:28 +00:00
}
}
2013-09-29 20:16:47 +00:00
}
2014-02-21 22:02:02 +00:00
} catch ( JedisConnectionException e ) {
// Redis server has disappeared!
getLogger ( ) . log ( Level . SEVERE , " Unable to get connection from pool - did your Redis server go away? " , e ) ;
throw new RuntimeException ( " Unable to get all players online " , e ) ;
2013-09-29 20:16:47 +00:00
}
}
2014-01-26 00:06:33 +00:00
return setBuilder . build ( ) ;
2013-09-29 20:16:47 +00:00
}
2014-01-02 05:14:58 +00:00
final void sendProxyCommand ( @NonNull String proxyId , @NonNull String command ) {
2014-04-04 03:03:27 +00:00
checkArgument ( getServerIds ( ) . contains ( proxyId ) | | proxyId . equals ( " allservers " ) , " proxyId is invalid " ) ;
2014-11-26 22:26:51 +00:00
sendChannelMessage ( " redisbungee- " + proxyId , command ) ;
2014-01-02 00:05:55 +00:00
}
2014-09-21 17:56:46 +00:00
2014-07-25 23:00:53 +00:00
final void sendChannelMessage ( String channel , String message ) {
2015-05-17 01:38:20 +00:00
try ( Jedis jedis = pool . getResource ( ) ) {
2014-07-25 23:00:53 +00:00
jedis . publish ( channel , message ) ;
} catch ( JedisConnectionException e ) {
// Redis server has disappeared!
getLogger ( ) . log ( Level . SEVERE , " Unable to get connection from pool - did your Redis server go away? " , e ) ;
throw new RuntimeException ( " Unable to publish channel message " , e ) ;
}
}
2014-01-02 00:05:55 +00:00
2015-11-15 16:01:54 +00:00
private long getRedisTime ( List < String > timeRes ) {
return Long . parseLong ( timeRes . get ( 0 ) ) ;
}
2013-09-29 20:16:47 +00:00
@Override
public void onEnable ( ) {
2016-06-26 08:19:10 +00:00
ThreadFactory factory = ( ( ThreadPoolExecutor ) getExecutorService ( ) ) . getThreadFactory ( ) ;
2016-06-28 20:39:05 +00:00
getExecutorService ( ) . shutdownNow ( ) ;
ScheduledExecutorService service ;
2016-06-26 08:19:10 +00:00
try {
2016-06-28 20:39:05 +00:00
Field field = Plugin . class . getDeclaredField ( " service " ) ;
2016-06-26 08:19:10 +00:00
field . setAccessible ( true ) ;
2016-06-28 20:39:05 +00:00
field . set ( this , service = Executors . newScheduledThreadPool ( 24 , factory ) ) ;
2016-06-26 08:19:10 +00:00
} catch ( Exception e ) {
2016-06-28 20:39:05 +00:00
throw new RuntimeException ( " Can't replace BungeeCord thread pool with our own " , e ) ;
2016-06-26 08:19:10 +00:00
}
2013-09-29 20:16:47 +00:00
try {
loadConfig ( ) ;
} catch ( IOException e ) {
2014-01-02 05:14:58 +00:00
throw new RuntimeException ( " Unable to load/save config " , e ) ;
2013-12-07 02:42:03 +00:00
} catch ( JedisConnectionException e ) {
throw new RuntimeException ( " Unable to connect to your Redis server! " , e ) ;
2013-09-29 20:16:47 +00:00
}
if ( pool ! = null ) {
2015-06-06 21:30:45 +00:00
try ( Jedis tmpRsc = pool . getResource ( ) ) {
2015-06-21 21:32:28 +00:00
// This is more portable than INFO <section>
String info = tmpRsc . info ( ) ;
for ( String s : info . split ( " \ r \ n " ) ) {
if ( s . startsWith ( " redis_version: " ) ) {
String version = s . split ( " : " ) [ 1 ] ;
if ( ! ( usingLua = RedisUtil . canUseLua ( version ) ) ) {
2015-11-15 16:01:54 +00:00
getLogger ( ) . warning ( " Your version of Redis ( " + version + " ) is not at least version 2.6. RedisBungee requires a newer version of Redis. " ) ;
2015-11-09 19:31:36 +00:00
throw new RuntimeException ( " Unsupported Redis version detected " ) ;
2015-06-21 21:32:28 +00:00
} else {
LuaManager manager = new LuaManager ( this ) ;
serverToPlayersScript = manager . createScript ( IOUtil . readInputStreamAsString ( getResourceAsStream ( " lua/server_to_players.lua " ) ) ) ;
2015-12-06 21:31:21 +00:00
getPlayerCountScript = manager . createScript ( IOUtil . readInputStreamAsString ( getResourceAsStream ( " lua/get_player_count.lua " ) ) ) ;
2015-06-21 21:32:28 +00:00
}
break ;
}
}
2015-06-22 02:13:49 +00:00
2017-03-09 06:42:04 +00:00
tmpRsc . hset ( " heartbeats " , configuration . getServerId ( ) , tmpRsc . time ( ) . get ( 0 ) ) ;
2015-11-09 19:31:36 +00:00
2015-06-22 02:13:49 +00:00
long uuidCacheSize = tmpRsc . hlen ( " uuid-cache " ) ;
if ( uuidCacheSize > 750000 ) {
getLogger ( ) . info ( " Looks like you have a really big UUID cache! Run https://www.spigotmc.org/resources/redisbungeecleaner.8505/ as soon as possible. " ) ;
}
2013-09-29 20:16:47 +00:00
}
2015-11-15 16:01:54 +00:00
serverIds = getCurrentServerIds ( true , false ) ;
2014-04-20 05:12:28 +00:00
uuidTranslator = new UUIDTranslator ( this ) ;
2016-06-28 20:39:05 +00:00
heartbeatTask = service . scheduleAtFixedRate ( new Runnable ( ) {
2013-10-31 23:07:37 +00:00
@Override
public void run ( ) {
2015-06-06 21:30:45 +00:00
try ( Jedis rsc = pool . getResource ( ) ) {
2015-11-15 16:01:54 +00:00
long redisTime = getRedisTime ( rsc . time ( ) ) ;
rsc . hset ( " heartbeats " , configuration . getServerId ( ) , String . valueOf ( redisTime ) ) ;
2014-02-21 22:02:02 +00:00
} catch ( JedisConnectionException e ) {
// Redis server has disappeared!
2014-08-10 21:58:45 +00:00
getLogger ( ) . log ( Level . SEVERE , " Unable to update heartbeat - did your Redis server go away? " , e ) ;
2016-10-14 03:44:49 +00:00
return ;
}
try {
serverIds = getCurrentServerIds ( true , false ) ;
globalPlayerCount . set ( getCurrentCount ( ) ) ;
} catch ( Throwable e ) {
getLogger ( ) . log ( Level . SEVERE , " Unable to update data - did your Redis server go away? " , e ) ;
2013-10-31 23:07:37 +00:00
}
}
2014-03-30 04:31:35 +00:00
} , 0 , 3 , TimeUnit . SECONDS ) ;
2014-08-10 03:16:47 +00:00
dataManager = new DataManager ( this ) ;
2015-04-18 14:13:02 +00:00
if ( configuration . isRegisterBungeeCommands ( ) ) {
2014-06-04 16:59:21 +00:00
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . GlistCommand ( this ) ) ;
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . FindCommand ( this ) ) ;
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . LastSeenCommand ( this ) ) ;
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . IpCommand ( this ) ) ;
}
2014-04-20 05:12:28 +00:00
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . SendToAll ( this ) ) ;
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . ServerId ( this ) ) ;
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . ServerIds ( ) ) ;
2014-08-21 01:31:02 +00:00
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . PlayerProxyCommand ( this ) ) ;
2015-06-22 10:06:02 +00:00
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . PlistCommand ( this ) ) ;
2015-08-02 23:57:48 +00:00
getProxy ( ) . getPluginManager ( ) . registerCommand ( this , new RedisBungeeCommands . DebugCommand ( this ) ) ;
2013-11-15 22:05:29 +00:00
api = new RedisBungeeAPI ( this ) ;
2015-04-18 14:13:02 +00:00
getProxy ( ) . getPluginManager ( ) . registerListener ( this , new RedisBungeeListener ( this , configuration . getExemptAddresses ( ) ) ) ;
2014-08-10 03:16:47 +00:00
getProxy ( ) . getPluginManager ( ) . registerListener ( this , dataManager ) ;
2013-11-15 22:05:29 +00:00
psl = new PubSubListener ( ) ;
2014-04-01 03:02:26 +00:00
getProxy ( ) . getScheduler ( ) . runAsync ( this , psl ) ;
2016-06-28 20:39:05 +00:00
integrityCheck = service . scheduleAtFixedRate ( new Runnable ( ) {
2014-01-20 15:34:40 +00:00
@Override
public void run ( ) {
2015-05-17 01:38:20 +00:00
try ( Jedis tmpRsc = pool . getResource ( ) ) {
2015-06-24 11:04:01 +00:00
Set < String > players = getLocalPlayersAsUuidStrings ( ) ;
2015-10-04 03:36:37 +00:00
Set < String > playersInRedis = tmpRsc . smembers ( " proxy: " + configuration . getServerId ( ) + " :usersOnline " ) ;
2015-11-15 16:01:54 +00:00
List < String > lagged = getCurrentServerIds ( false , true ) ;
2015-10-18 20:51:15 +00:00
// Clean up lagged players.
for ( String s : lagged ) {
Set < String > laggedPlayers = tmpRsc . smembers ( " proxy: " + s + " :usersOnline " ) ;
tmpRsc . del ( " proxy: " + s + " :usersOnline " ) ;
if ( ! laggedPlayers . isEmpty ( ) ) {
getLogger ( ) . info ( " Cleaning up lagged proxy " + s + " ( " + laggedPlayers . size ( ) + " players)... " ) ;
for ( String laggedPlayer : laggedPlayers ) {
RedisUtil . cleanUpPlayer ( laggedPlayer , tmpRsc ) ;
}
}
}
2014-09-11 20:19:50 +00:00
2016-03-06 02:39:01 +00:00
Set < String > absentLocally = new HashSet < > ( playersInRedis ) ;
absentLocally . removeAll ( players ) ;
Set < String > absentInRedis = new HashSet < > ( players ) ;
absentInRedis . removeAll ( playersInRedis ) ;
for ( String member : absentLocally ) {
boolean found = false ;
for ( String proxyId : getServerIds ( ) ) {
if ( proxyId . equals ( configuration . getServerId ( ) ) ) continue ;
if ( tmpRsc . sismember ( " proxy: " + proxyId + " :usersOnline " , member ) ) {
// Just clean up the set.
found = true ;
break ;
2014-01-20 23:42:56 +00:00
}
2016-03-06 02:39:01 +00:00
}
if ( ! found ) {
RedisUtil . cleanUpPlayer ( member , tmpRsc ) ;
getLogger ( ) . warning ( " Player found in set that was not found locally and globally: " + member ) ;
} else {
tmpRsc . srem ( " proxy: " + configuration . getServerId ( ) + " :usersOnline " , member ) ;
getLogger ( ) . warning ( " Player found in set that was not found locally, but is on another proxy: " + member ) ;
2014-01-20 23:42:56 +00:00
}
2014-09-11 20:19:50 +00:00
}
2015-09-27 15:49:03 +00:00
Pipeline pipeline = tmpRsc . pipelined ( ) ;
2018-06-12 15:13:47 +00:00
for ( String player : absentInRedis ) {
2015-06-24 09:51:40 +00:00
// Player not online according to Redis but not BungeeCord.
2014-09-11 20:19:50 +00:00
getLogger ( ) . warning ( " Player " + player + " is on the proxy but not in Redis. " ) ;
2015-09-27 15:49:03 +00:00
ProxiedPlayer proxiedPlayer = ProxyServer . getInstance ( ) . getPlayer ( UUID . fromString ( player ) ) ;
if ( proxiedPlayer = = null )
continue ; // We'll deal with it later.
2015-10-04 03:45:34 +00:00
RedisUtil . createPlayer ( proxiedPlayer , pipeline , true ) ;
2014-09-11 20:19:50 +00:00
}
2015-09-27 15:49:03 +00:00
pipeline . sync ( ) ;
2016-10-14 03:44:49 +00:00
} catch ( Throwable e ) {
getLogger ( ) . log ( Level . SEVERE , " Unable to fix up stored player data " , e ) ;
2014-01-20 15:34:40 +00:00
}
}
2014-09-11 20:19:50 +00:00
} , 0 , 1 , TimeUnit . MINUTES ) ;
2013-09-29 20:16:47 +00:00
}
2019-06-07 21:33:58 +00:00
getProxy ( ) . registerChannel ( " legacy:redisbungee " ) ;
2014-01-22 20:32:49 +00:00
getProxy ( ) . registerChannel ( " RedisBungee " ) ;
2013-09-29 20:16:47 +00:00
}
@Override
public void onDisable ( ) {
2013-10-18 21:14:58 +00:00
if ( pool ! = null ) {
2013-11-15 22:05:29 +00:00
// Poison the PubSub listener
2014-11-02 19:16:41 +00:00
psl . poison ( ) ;
2016-06-28 20:39:05 +00:00
integrityCheck . cancel ( true ) ;
heartbeatTask . cancel ( true ) ;
2014-11-26 17:00:05 +00:00
getProxy ( ) . getPluginManager ( ) . unregisterListeners ( this ) ;
2014-11-02 19:16:41 +00:00
2015-06-06 21:30:45 +00:00
try ( Jedis tmpRsc = pool . getResource ( ) ) {
2015-04-18 14:13:02 +00:00
tmpRsc . hdel ( " heartbeats " , configuration . getServerId ( ) ) ;
if ( tmpRsc . scard ( " proxy: " + configuration . getServerId ( ) + " :usersOnline " ) > 0 ) {
Set < String > players = tmpRsc . smembers ( " proxy: " + configuration . getServerId ( ) + " :usersOnline " ) ;
2014-06-11 11:24:09 +00:00
for ( String member : players )
2014-09-11 20:38:40 +00:00
RedisUtil . cleanUpPlayer ( member , tmpRsc ) ;
2013-12-03 21:01:27 +00:00
}
2013-10-18 21:14:58 +00:00
}
2014-11-02 19:16:41 +00:00
2013-10-19 22:25:38 +00:00
pool . destroy ( ) ;
2013-10-18 21:14:58 +00:00
}
2013-09-29 20:16:47 +00:00
}
2013-12-07 02:42:03 +00:00
private void loadConfig ( ) throws IOException , JedisConnectionException {
2013-09-29 20:16:47 +00:00
if ( ! getDataFolder ( ) . exists ( ) ) {
getDataFolder ( ) . mkdir ( ) ;
}
2013-10-19 22:25:38 +00:00
File file = new File ( getDataFolder ( ) , " config.yml " ) ;
if ( ! file . exists ( ) ) {
file . createNewFile ( ) ;
try ( InputStream in = getResourceAsStream ( " example_config.yml " ) ;
OutputStream out = new FileOutputStream ( file ) ) {
2013-11-14 00:28:53 +00:00
ByteStreams . copy ( in , out ) ;
2013-09-29 20:16:47 +00:00
}
}
2015-04-18 14:13:02 +00:00
final Configuration configuration = ConfigurationProvider . getProvider ( YamlConfiguration . class ) . load ( file ) ;
2013-10-19 22:25:38 +00:00
2014-07-10 02:54:12 +00:00
final String redisServer = configuration . getString ( " redis-server " , " localhost " ) ;
final int redisPort = configuration . getInt ( " redis-port " , 6379 ) ;
2021-01-07 19:30:39 +00:00
final boolean useSSL = configuration . getBoolean ( " useSSL " ) ;
2013-12-24 04:12:54 +00:00
String redisPassword = configuration . getString ( " redis-password " ) ;
2015-04-18 14:13:02 +00:00
String serverId = configuration . getString ( " server-id " ) ;
2013-09-29 20:16:47 +00:00
2014-06-17 19:47:52 +00:00
if ( redisPassword ! = null & & ( redisPassword . isEmpty ( ) | | redisPassword . equals ( " none " ) ) ) {
2013-12-24 04:12:54 +00:00
redisPassword = null ;
2013-11-16 02:56:59 +00:00
}
2013-09-29 20:16:47 +00:00
2013-12-24 04:12:54 +00:00
// Configuration sanity checks.
2014-06-17 19:47:52 +00:00
if ( serverId = = null | | serverId . isEmpty ( ) ) {
2013-12-24 04:12:54 +00:00
throw new RuntimeException ( " server-id is not specified in the configuration or is empty " ) ;
}
2013-11-15 22:55:57 +00:00
2014-06-17 19:47:52 +00:00
if ( redisServer ! = null & & ! redisServer . isEmpty ( ) ) {
2014-07-10 02:54:12 +00:00
final String finalRedisPassword = redisPassword ;
FutureTask < JedisPool > task = new FutureTask < > ( new Callable < JedisPool > ( ) {
@Override
public JedisPool call ( ) throws Exception {
2015-05-17 01:38:20 +00:00
// Create the pool...
2014-07-10 02:54:12 +00:00
JedisPoolConfig config = new JedisPoolConfig ( ) ;
2014-11-26 22:30:25 +00:00
config . setMaxTotal ( configuration . getInt ( " max-redis-connections " , 8 ) ) ;
2021-01-07 19:30:39 +00:00
return new JedisPool ( config , redisServer , redisPort , 0 , finalRedisPassword , useSSL ) ;
2014-07-10 02:54:12 +00:00
}
} ) ;
getProxy ( ) . getScheduler ( ) . runAsync ( this , task ) ;
try {
pool = task . get ( ) ;
} catch ( InterruptedException | ExecutionException e ) {
throw new RuntimeException ( " Unable to create Redis pool " , e ) ;
}
2014-06-17 19:47:52 +00:00
// Test the connection
2015-05-17 18:46:44 +00:00
try ( Jedis rsc = pool . getResource ( ) ) {
2014-09-21 17:55:14 +00:00
rsc . ping ( ) ;
2014-06-17 19:47:52 +00:00
// If that worked, now we can check for an existing, alive Bungee:
File crashFile = new File ( getDataFolder ( ) , " restarted_from_crash.txt " ) ;
2015-06-24 09:51:40 +00:00
if ( crashFile . exists ( ) ) {
2014-06-17 19:47:52 +00:00
crashFile . delete ( ) ;
2015-06-24 09:51:40 +00:00
} else if ( rsc . hexists ( " heartbeats " , serverId ) ) {
2014-06-17 19:47:52 +00:00
try {
2015-06-24 10:06:34 +00:00
long value = Long . parseLong ( rsc . hget ( " heartbeats " , serverId ) ) ;
2017-03-09 06:20:51 +00:00
long redisTime = getRedisTime ( rsc . time ( ) ) ;
if ( redisTime < value + 20 ) {
2015-06-22 08:35:45 +00:00
getLogger ( ) . severe ( " You have launched a possible impostor BungeeCord instance. Another instance is already running. " ) ;
2014-06-17 19:47:52 +00:00
getLogger ( ) . severe ( " For data consistency reasons, RedisBungee will now disable itself. " ) ;
getLogger ( ) . severe ( " If this instance is coming up from a crash, create a file in your RedisBungee plugins directory with the name 'restarted_from_crash.txt' and RedisBungee will not perform this check. " ) ;
2015-06-24 10:06:34 +00:00
throw new RuntimeException ( " Possible impostor instance! " ) ;
2014-05-23 03:37:26 +00:00
}
2014-06-17 19:47:52 +00:00
} catch ( NumberFormatException ignored ) {
2014-03-02 19:28:18 +00:00
}
2014-06-17 19:47:52 +00:00
}
2014-09-21 17:55:14 +00:00
FutureTask < Void > task2 = new FutureTask < > ( new Callable < Void > ( ) {
@Override
public Void call ( ) throws Exception {
2015-01-25 05:17:52 +00:00
httpClient = new OkHttpClient ( ) ;
2015-06-22 08:35:45 +00:00
Dispatcher dispatcher = new Dispatcher ( getExecutorService ( ) ) ;
2015-03-28 20:46:08 +00:00
httpClient . setDispatcher ( dispatcher ) ;
2015-02-04 15:02:30 +00:00
NameFetcher . setHttpClient ( httpClient ) ;
2015-02-10 15:04:03 +00:00
UUIDFetcher . setHttpClient ( httpClient ) ;
2015-04-18 14:13:02 +00:00
RedisBungee . configuration = new RedisBungeeConfiguration ( RedisBungee . this . getPool ( ) , configuration ) ;
2014-09-21 17:55:14 +00:00
return null ;
}
} ) ;
getProxy ( ) . getScheduler ( ) . runAsync ( this , task2 ) ;
try {
task2 . get ( ) ;
} catch ( InterruptedException | ExecutionException e ) {
2015-06-24 09:51:40 +00:00
throw new RuntimeException ( " Unable to create HTTP client " , e ) ;
2014-09-21 17:55:14 +00:00
}
2014-06-17 19:47:52 +00:00
getLogger ( ) . log ( Level . INFO , " Successfully connected to Redis. " ) ;
} catch ( JedisConnectionException e ) {
pool . destroy ( ) ;
pool = null ;
throw e ;
2013-09-29 20:16:47 +00:00
}
2013-12-24 04:12:54 +00:00
} else {
throw new RuntimeException ( " No redis server specified! " ) ;
2013-09-29 20:16:47 +00:00
}
}
2015-06-24 09:54:19 +00:00
@NoArgsConstructor ( access = AccessLevel . PRIVATE )
2014-03-31 15:19:33 +00:00
class PubSubListener implements Runnable {
2013-11-15 22:05:29 +00:00
private JedisPubSubHandler jpsh ;
2019-04-19 18:51:10 +00:00
private Set < String > addedChannels = new HashSet < String > ( ) ;
2013-11-15 22:05:29 +00:00
@Override
public void run ( ) {
2015-12-06 17:12:55 +00:00
boolean broken = false ;
2015-06-24 09:54:19 +00:00
try ( Jedis rsc = pool . getResource ( ) ) {
2015-12-06 17:12:55 +00:00
try {
jpsh = new JedisPubSubHandler ( ) ;
2019-04-19 18:51:10 +00:00
addedChannels . add ( " redisbungee- " + configuration . getServerId ( ) ) ;
addedChannels . add ( " redisbungee-allservers " ) ;
addedChannels . add ( " redisbungee-data " ) ;
rsc . subscribe ( jpsh , addedChannels . toArray ( new String [ 0 ] ) ) ;
2015-12-06 17:12:55 +00:00
} catch ( Exception e ) {
// FIXME: Extremely ugly hack
// Attempt to unsubscribe this instance and try again.
getLogger ( ) . log ( Level . INFO , " PubSub error, attempting to recover. " , e ) ;
2016-03-11 01:19:40 +00:00
try {
jpsh . unsubscribe ( ) ;
} catch ( Exception e1 ) {
/ * This may fail with
- java . net . SocketException : Broken pipe
- redis . clients . jedis . exceptions . JedisConnectionException : JedisPubSub was not subscribed to a Jedis instance
* /
}
2015-12-06 17:12:55 +00:00
broken = true ;
}
2019-04-19 18:51:10 +00:00
} catch ( JedisConnectionException e ) {
getLogger ( ) . log ( Level . INFO , " PubSub error, attempting to recover in 5 secs. " ) ;
getProxy ( ) . getScheduler ( ) . schedule ( RedisBungee . this , PubSubListener . this , 5 , TimeUnit . SECONDS ) ;
2015-12-06 17:12:55 +00:00
}
if ( broken ) {
run ( ) ;
2013-11-15 22:05:29 +00:00
}
}
2014-03-31 15:19:33 +00:00
public void addChannel ( String . . . channel ) {
2019-04-19 18:51:10 +00:00
addedChannels . addAll ( Arrays . asList ( channel ) ) ;
2014-03-31 15:19:33 +00:00
jpsh . subscribe ( channel ) ;
}
public void removeChannel ( String . . . channel ) {
2019-04-19 18:51:10 +00:00
addedChannels . removeAll ( Arrays . asList ( channel ) ) ;
2014-03-31 15:19:33 +00:00
jpsh . unsubscribe ( channel ) ;
}
2014-11-02 19:16:41 +00:00
public void poison ( ) {
2019-04-19 18:51:10 +00:00
addedChannels . clear ( ) ;
2014-11-02 19:16:41 +00:00
jpsh . unsubscribe ( ) ;
}
2013-11-15 22:05:29 +00:00
}
2015-06-24 10:06:34 +00:00
private class JedisPubSubHandler extends JedisPubSub {
2013-11-15 22:05:29 +00:00
@Override
2014-03-31 15:19:33 +00:00
public void onMessage ( final String s , final String s2 ) {
2014-01-26 00:06:33 +00:00
if ( s2 . trim ( ) . length ( ) = = 0 ) return ;
2014-07-02 06:38:04 +00:00
getProxy ( ) . getScheduler ( ) . runAsync ( RedisBungee . this , new Runnable ( ) {
2014-03-31 14:23:10 +00:00
@Override
public void run ( ) {
2014-03-31 15:19:33 +00:00
getProxy ( ) . getPluginManager ( ) . callEvent ( new PubSubMessageEvent ( s , s2 ) ) ;
2014-03-31 14:23:10 +00:00
}
} ) ;
2013-11-15 22:05:29 +00:00
}
}
2013-09-29 20:16:47 +00:00
}