Skip to content

Commit

Permalink
Merge pull request #184 from chesterbr/launcher
Browse files Browse the repository at this point in the history
Update sem downtime no servidor, parte 1
  • Loading branch information
chesterbr authored Jul 23, 2023
2 parents 5012e3c + 5c43bdb commit 4a30daa
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 36 deletions.
64 changes: 64 additions & 0 deletions launcher.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash

# Inicia o servidor do miniTruco e o reinicia quando o JAR é modificado.
#
# Esse reinício é feito com um "soft shutdown" do servidor (enviando um SIGUSR1
# para que ele libere a porta) e subindo uma nova instância imediatamente*; dessa
# forma os jogadores podem finalizar partidas em andamento, mas novas conexões
# vão para o servidor novo.
#
# * na real uns 2-3 segundos pra garantir que o .jar novo está finalizado

if [ -z "$1" ]; then
echo "Erro: é necessário fornecer o caminho do arquivo JAR como parâmetro."
echo "Exemplo de uso: $0 /caminho/do/arquivo.jar"
exit 1
fi

jar="$1"
servidor_pid=""

inicia_servidor() {
java -jar "$jar" &
servidor_pid=$! # $! contém o PID do último processo em background
}

shutdown_suave_do_servidor() {
if [ -n "$servidor_pid" ]; then
echo "Enviando sinal SIGUSR1 para o servidor no PID: $servidor_pid"
kill -SIGUSR1 "$servidor_pid"
fi
}

servidor_em_execucao() {
if ps -p "$servidor_pid" >/dev/null; then
return 0
else
return 1
fi
}

aguarda_mudanca_no_jar() {
(inotifywait -e modify "$jar") & # em background para não bloquer o SIGTERM
inotify_pid=$!
wait $inotify_pid
}

aguarda_o_novo_jar_estar_pronto() {
while [ ! -e "$jar" ]; do
sleep 1
done
sleep 2 # Para ter certeza que o jar foi completamente salvo
}

# Se o launcher for finalizado, finaliza o servidor também
trap 'shutdown_suave_do_servidor; exit 0' SIGTERM

###### O script efetivamente começa aqui ######

while true; do
inicia_servidor
aguarda_mudanca_no_jar
shutdown_suave_do_servidor
aguarda_o_novo_jar_estar_pronto
done
105 changes: 69 additions & 36 deletions server/src/main/java/me/chester/minitruco/server/MiniTrucoServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,58 +6,91 @@
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.net.SocketTimeoutException;

import sun.misc.Signal;

public class MiniTrucoServer {

/**
* Porta onde o servidor escuta por conexões. Acho que ninguém nunca
* entendeu porque eu escolhi este número. 🤡
*/
public static final int PORTA_SERVIDOR = 6912;

/**
* Versão do servidor (3.0 é a primeira pós-J2ME)
*/
public static final String VERSAO_SERVER = "3.0";

public static DateFormat dfStartup;

public static Date dataStartup;

public static String strDataStartup;

/**
* Ponto de entrada do servidor. Apenas dispara a thread que aceita
* conexões e encerra ela quando o launcher.sh solicitar.
*/
public static void main(String[] args) {
// Enquanto esta thread estiver rodando, o servidor vai aceitar conexões
// e colocar o socket de cada cliente numa thread separada
Thread threadAceitaConexoes = new Thread(() -> aceitaConexoes());
threadAceitaConexoes.start();

// Se recebermos um USR1, o .jar foi atualizado. Nesse caso, vamos parar
// de aceitar conexões (liberando a porta para a nova versão) mas as
// threads dos jogadores conectados e partidas em andamento continuam
// rodando.
Signal.handle(new Signal("USR1"), signal -> {
ServerLogger.evento("Recebido sinal USR1 - interrompendo threadAceitaConxoes");
threadAceitaConexoes.interrupt();
});

// Quando *todas* as threads encerrarem, loga o evento final
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
ServerLogger.evento("Servidor finalizado; JVM desligando. Tchau.");
}));

// A thread inicial termina por aqui, mas o servidor continua rodandno
// até que todas as threads se encerrem.
}

/**
* Loop que aceita conexões de clientes e coloca o socket de cada um num
* objeto JogadorConectado que roda em uma thread separada.
* <p>
* Permanece em execução até que a thread onde ele está rodando receba
* um interrupt.
*/
public static void aceitaConexoes() {
ServerLogger.evento("Servidor inicializado e escutando na porta " + PORTA_SERVIDOR);
ServerSocket s = null;
try {

// Guarda a data de início do servidor num formato apropriado para HTTP
// vide JogadorContectado.serveArquivosApplet

dfStartup = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z",
Locale.US);
dataStartup = new Date();
strDataStartup = dfStartup.format(dataStartup);

ServerLogger
.evento("Servidor Inicializado, pronto para escutar na porta "
+ PORTA_SERVIDOR);

try {
ServerSocket s = new ServerSocket(PORTA_SERVIDOR);
while (true) {
Socket sCliente = s.accept();
JogadorConectado j = new JogadorConectado(sCliente);
Thread t = new Thread(j);
t.start();
s = new ServerSocket(PORTA_SERVIDOR);
// Vamos checar a cada 1s se recebemos um interrupt
s.setSoTimeout(1000);
while (true) {
Socket sCliente;
try {
sCliente = s.accept();
} catch (SocketTimeoutException e) {
// Era um interrupt, vamos sair do loop
if (Thread.interrupted()) {
break;
}
// Era só o timeout, vamos continuar
continue;
}
} catch (IOException e) {
ServerLogger.evento(e, "Erro de I/O no ServerSocket, saindo do programa");
JogadorConectado j = new JogadorConectado(sCliente);
(new Thread(j)).start();
}

} catch (IOException e) {
ServerLogger.evento(e, "Erro de I/O no ServerSocket");
} finally {
ServerLogger.evento("Servidor Finalizado");
if (s != null) {
try {
s.close();
} catch (IOException e) {
ServerLogger.evento(e, "Erro de I/O ao fechar ServerSocket");
}
}
ServerLogger.evento("Servidor não está mais escutando; aguardando finalização dos jogadores conectados.");
}

}

}

0 comments on commit 4a30daa

Please sign in to comment.