From 73d1753935f4fbf9b8ffa0e4b51a96a17156e155 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Thu, 13 Nov 2025 20:45:37 -0300 Subject: [PATCH 1/3] Add `IPCManager` for UNIX domain socket communication: - Introduced `IPCManager` class to handle IPC using UNIX domain sockets. - Updated `MediaManagerApplication` to initialize and manage `IPCManager` during startup and shutdown. - Enhanced logging for IPC initialization and cleanup. - Updated `config.properties.example` with `ipc.socket.path` property. --- .../mediamanager/MediaManagerApplication.java | 17 ++- .../mediamanager/service/ipc/IPCManager.java | 101 ++++++++++++++++++ src/main/resources/config.properties.example | 2 + 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/mediamanager/service/ipc/IPCManager.java diff --git a/src/main/java/com/mediamanager/MediaManagerApplication.java b/src/main/java/com/mediamanager/MediaManagerApplication.java index f4acc3c..d483da3 100644 --- a/src/main/java/com/mediamanager/MediaManagerApplication.java +++ b/src/main/java/com/mediamanager/MediaManagerApplication.java @@ -1,5 +1,6 @@ package com.mediamanager; +import com.mediamanager.service.ipc.IPCManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -13,6 +14,7 @@ public class MediaManagerApplication { private static final Logger logger = LogManager.getLogger(MediaManagerApplication.class); private static Properties config; private static DatabaseManager databaseManager; + private static IPCManager ipcManager; public static void main(String[] args) { logger.info("Starting MediaManager Core Application..."); @@ -23,12 +25,13 @@ public class MediaManagerApplication { databaseManager = new DatabaseManager(config); databaseManager.init(); - - // TODO: Initialize IPC server with named pipes + ipcManager = new IPCManager(config); + ipcManager.init(); + // TODO: Start application services logger.info("MediaManager Core started successfully"); - logger.info("IPC Pipe: {}", config.getProperty("ipc.pipe.path") + "/" + config.getProperty("ipc.pipe.name")); + logger.info("IPC Socket: {}", ipcManager.getSocketPath().toAbsolutePath().toString()); // Keep application running Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -40,6 +43,14 @@ public class MediaManagerApplication { databaseManager.close(); } + if (ipcManager != null) { + try { + ipcManager.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + logger.info("MediaManager Core shutdown successfully"); logger.info("Goodbye!"); diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java new file mode 100644 index 0000000..e1cc0ae --- /dev/null +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -0,0 +1,101 @@ +package com.mediamanager.service.ipc; + + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public class IPCManager { + private static Properties configuration; + private static final Logger logger = LogManager.getLogger(IPCManager.class); + private Path socketPath; + private UnixDomainSocketAddress socketAddress; + private ServerSocketChannel serverChannel; + + public IPCManager(Properties config){ + configuration = config; + logger.debug("IPCManager created with configuration:"); + + + } + public void init() throws Exception { + logger.info("Initializing IPC connection..."); + validateConfiguration(); + socketPath = Path.of(configuration.getProperty("ipc.socket.path")).resolve("mediamanager.sock"); + + if (checkUnixSocketExists()){ + logger.warn("IPC socket already exists"); + logger.info("Deleting existing socket..."); + Files.deleteIfExists(socketPath); + + + } + + try{ + socketAddress = UnixDomainSocketAddress.of(socketPath); + logger.info("IPC socket created successfully"); + } catch (Exception e){ + logger.error("Failed to create socket: {}", e.getMessage()); + throw new Exception("Failed to create socket: " + e.getMessage(), e); + } + if (!Files.isDirectory(socketPath.getParent())) { + logger.info("Creating parent directory for socket..."); + Files.createDirectories(socketPath.getParent()); + } + + serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + logger.info("IPC server channel opened successfully"); + serverChannel.bind(socketAddress); + logger.info("IPC server channel bound successfully"); + logger.info("IPC server listening on {}", socketPath.toAbsolutePath().toString()); + + + } + private void validateConfiguration() throws Exception { + String[] requiredProperties = { + "ipc.socket.path" + }; + for (String property : requiredProperties) { + if (configuration.getProperty(property) == null) { + throw new Exception("Missing required configuration property: " + property); + } + } + logger.debug("IPC configuration validated successfully"); + } + private boolean checkUnixSocketExists() { + File socketFile = new File(String.valueOf(socketPath)); + return socketFile.exists(); + } + + public Path getSocketPath(){ + return socketPath; + } + + public void close() throws Exception { + logger.info("Closing IPC connection..."); + if (serverChannel != null) { + serverChannel.close(); + } + File socketFile = new File(String.valueOf(socketPath)); + boolean delete = false; + if (socketFile.exists()) { + delete = socketFile.delete(); + } + if (!delete){ + logger.warn("Failed to delete socket file"); + }else { + logger.info("IPC socket deleted successfully"); + Files.deleteIfExists(socketPath.getParent()); + logger.info("IPC socket parent directory deleted successfully"); + + } + + } +} diff --git a/src/main/resources/config.properties.example b/src/main/resources/config.properties.example index 8840823..095d75c 100644 --- a/src/main/resources/config.properties.example +++ b/src/main/resources/config.properties.example @@ -22,3 +22,5 @@ ipc.pipe.name=mediamanager-pipe ipc.pipe.path=/tmp/mediamanager ipc.buffer.size=8192 +ipc.socket.path=/tmp/mediamanager + From f1071dca03da33f7355a6927497509b5aedaa4a2 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 14 Nov 2025 06:22:35 -0300 Subject: [PATCH 2/3] Enhance `IPCManager` with non-blocking server support and improved connection handling: - Configured UNIX domain socket for non-blocking mode to prevent indefinite blocking on `accept()`. - Introduced connection loop with thread pool for client handling. - Added `ClientHandler` to process client communications. - Implemented proper resource cleanup during shutdown. - Improved logging for socket initialization, client handling, and shutdown. --- .../mediamanager/service/ipc/IPCManager.java | 227 +++++++++++++++--- 1 file changed, 191 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index e1cc0ae..3666c19 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -5,19 +5,35 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; +import java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public class IPCManager { - private static Properties configuration; + private final Properties configuration; private static final Logger logger = LogManager.getLogger(IPCManager.class); private Path socketPath; private UnixDomainSocketAddress socketAddress; private ServerSocketChannel serverChannel; + private ExecutorService clientThreadPool; + private final AtomicBoolean running = new AtomicBoolean(false); + private final ConcurrentHashMap activeClients = new ConcurrentHashMap<>(); + private final AtomicInteger clientIdCounter = new AtomicInteger(0); + public IPCManager(Properties config){ configuration = config; @@ -30,33 +46,58 @@ public class IPCManager { validateConfiguration(); socketPath = Path.of(configuration.getProperty("ipc.socket.path")).resolve("mediamanager.sock"); - if (checkUnixSocketExists()){ - logger.warn("IPC socket already exists"); + if (Files.exists(socketPath)) { + logger.warn("Socket file already exists at: {}", socketPath); logger.info("Deleting existing socket..."); Files.deleteIfExists(socketPath); - - } - try{ + Path parentDir = socketPath.getParent(); + if (parentDir != null && !Files.exists(parentDir)) { + logger.info("Creating parent directory for socket: {}", parentDir); + Files.createDirectories(parentDir); + } + + try { socketAddress = UnixDomainSocketAddress.of(socketPath); - logger.info("IPC socket created successfully"); - } catch (Exception e){ - logger.error("Failed to create socket: {}", e.getMessage()); - throw new Exception("Failed to create socket: " + e.getMessage(), e); + logger.debug("Socket address created"); + + serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + serverChannel.bind(socketAddress); + logger.info("Server bound to socket - file created at: {}", socketPath); + + // ESTA É A MUDANÇA CRÍTICA + // Configura o canal para modo não-bloqueante + // Isso faz accept() retornar null imediatamente se não houver cliente + // ao invés de bloquear esperando indefinidamente + serverChannel.configureBlocking(false); + logger.debug("Server channel configured for non-blocking mode"); + + Set perms = PosixFilePermissions.fromString("rw-------"); + Files.setPosixFilePermissions(socketPath, perms); + logger.debug("Socket permissions set to: rw-------"); + + clientThreadPool = Executors.newCachedThreadPool(runnable -> { + Thread thread = new Thread(runnable); + thread.setName("IPC-Client-Handler-" + thread.getId()); + thread.setDaemon(true); + return thread; + }); + logger.debug("Client thread pool created"); + + running.set(true); + + Thread serverThread = new Thread(this::acceptConnectionsLoop, "IPC-Server-Accept-Thread"); + serverThread.setDaemon(true); + serverThread.start(); + logger.info("Server thread started - accepting connections"); + + logger.info("IPC server initialized successfully on {}", socketPath.toAbsolutePath()); + + } catch (IOException e) { + logger.error("Failed to initialize IPC server: {}", e.getMessage()); + throw new Exception("Failed to initialize IPC server: " + e.getMessage(), e); } - if (!Files.isDirectory(socketPath.getParent())) { - logger.info("Creating parent directory for socket..."); - Files.createDirectories(socketPath.getParent()); - } - - serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - logger.info("IPC server channel opened successfully"); - serverChannel.bind(socketAddress); - logger.info("IPC server channel bound successfully"); - logger.info("IPC server listening on {}", socketPath.toAbsolutePath().toString()); - - } private void validateConfiguration() throws Exception { String[] requiredProperties = { @@ -79,23 +120,137 @@ public class IPCManager { } public void close() throws Exception { + if(!running.get()){ + logger.warn("IPC connection is already closed"); + } + + logger.info("Closing IPC connection..."); - if (serverChannel != null) { + + running.set(false); + if (serverChannel != null && serverChannel.isOpen()) { serverChannel.close(); - } - File socketFile = new File(String.valueOf(socketPath)); - boolean delete = false; - if (socketFile.exists()) { - delete = socketFile.delete(); - } - if (!delete){ - logger.warn("Failed to delete socket file"); - }else { - logger.info("IPC socket deleted successfully"); - Files.deleteIfExists(socketPath.getParent()); - logger.info("IPC socket parent directory deleted successfully"); - + logger.debug("Server channel closed"); } + if (clientThreadPool != null) { + clientThreadPool.shutdown(); + try { + if (!clientThreadPool.awaitTermination(30, TimeUnit.SECONDS)) { + logger.warn("Some client handlers did not finish in time, forcing shutdown"); + clientThreadPool.shutdownNow(); + } + } catch (InterruptedException e) { + logger.error("Interrupted while waiting for client handlers", e); + clientThreadPool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + if (socketPath != null && Files.exists(socketPath)) { + Files.deleteIfExists(socketPath); + logger.info("Socket file deleted successfully"); + } + + logger.info("IPC server closed successfully"); + } + + private void acceptConnectionsLoop() { + logger.info("Preparing to accept connections..."); + + while (running.get()) { + try { + // Em modo não-bloqueante, accept() retorna imediatamente + // Se há um cliente esperando, retorna o SocketChannel + // Se não há cliente, retorna null + SocketChannel clientChannel = serverChannel.accept(); + + if (clientChannel != null) { + // Um cliente realmente se conectou! + int clientId = clientIdCounter.incrementAndGet(); + logger.info("Client {} connected", clientId); + + ClientHandler handler = new ClientHandler(clientId, clientChannel); + activeClients.put(clientId, handler); + + clientThreadPool.submit(() -> { + try { + handler.handle(); + } finally { + activeClients.remove(clientId); + logger.info("Client {} disconnected", clientId); + } + }); + } else { + // Nenhum cliente conectado no momento + // Dorme por um curto período antes de verificar novamente + // Isso evita consumir CPU desnecessariamente em um loop vazio + Thread.sleep(100); // 100 milissegundos + } + + } catch (InterruptedException e) { + // Thread foi interrompida, provavelmente durante shutdown + logger.debug("Accept loop interrupted"); + break; + + } catch (IOException e) { + // Erros de I/O reais devem ser logados + if (running.get()) { + logger.error("Error accepting client connection", e); + } + break; + } + } + + logger.info("Connection loop stopped gracefully"); + } + + + private class ClientHandler { + private final int clientId; + private final SocketChannel channel; + + public ClientHandler(int clientId, SocketChannel channel) { + this.clientId = clientId; + this.channel = channel; + } + + /** + * Método principal que processa a comunicação com o cliente. + * Aqui é onde vamos ler mensagens JSON, processá-las, e enviar respostas. + */ + public void handle() { + logger.debug("Client {} handler thread started", clientId); + + try { + // TODO: No próximo passo, vamos implementar: + // 1. Ler mensagens JSON do SocketChannel + // 2. Parsear o JSON para objetos Java + // 3. Processar a requisição + // 4. Criar uma resposta JSON + // 5. Escrever a resposta de volta no SocketChannel + + // Por enquanto, apenas mantém a conexão aberta brevemente + // para testar que o sistema de aceitação e threads está funcionando + Thread.sleep(100); + + logger.debug("Client {} processing complete", clientId); + + } catch (Exception e) { + logger.error("Error handling client {}", clientId, e); + } finally { + // SEMPRE fecha o canal quando terminar + // O finally garante que isso acontece mesmo se houver exceção + try { + channel.close(); + logger.debug("Client {} channel closed", clientId); + } catch (IOException e) { + logger.error("Error closing client {} channel", clientId, e); + } + } + } } } + + + From 38546351851eb92e7af99579aaeb3a390fdb3112 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Santos Souza de Miranda Date: Fri, 14 Nov 2025 19:52:53 -0300 Subject: [PATCH 3/3] Remove the unused ` checkUnixSocketExists ` method from `IPCManager`. --- src/main/java/com/mediamanager/service/ipc/IPCManager.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/mediamanager/service/ipc/IPCManager.java b/src/main/java/com/mediamanager/service/ipc/IPCManager.java index 3666c19..19537c5 100644 --- a/src/main/java/com/mediamanager/service/ipc/IPCManager.java +++ b/src/main/java/com/mediamanager/service/ipc/IPCManager.java @@ -110,10 +110,6 @@ public class IPCManager { } logger.debug("IPC configuration validated successfully"); } - private boolean checkUnixSocketExists() { - File socketFile = new File(String.valueOf(socketPath)); - return socketFile.exists(); - } public Path getSocketPath(){ return socketPath;