com.epam.reportportal.service.launch.lock.LaunchIdLockSocket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client-java Show documentation
Show all versions of client-java Show documentation
A application used as an example on how to set up pushing its components to the Central Repository .
The newest version!
/*
* Copyright 2021 EPAM Systems
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.epam.reportportal.service.launch.lock;
import com.epam.reportportal.listeners.ListenerParameters;
import com.epam.reportportal.service.LaunchIdLock;
import com.epam.reportportal.utils.Waiter;
import com.epam.reportportal.utils.properties.ListenerProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* A service to perform blocking I/O operations on network sockets to get single launch UUID for multiple clients on a machine.
* This class uses local networking, therefore applicable in scope of a single hardware machine. You can control port number
* with {@link ListenerProperty#CLIENT_JOIN_LOCK_PORT} property.
*
* @author Vadzim Hushchanskou
*/
public class LaunchIdLockSocket extends AbstractLaunchIdLock implements LaunchIdLock {
private static final Logger LOGGER = LoggerFactory.getLogger(LaunchIdLockSocket.class);
public static final Charset TRANSFER_CHARSET = StandardCharsets.ISO_8859_1;
private static final int SOCKET_BACKLOG = 50;
private static final String COMMAND_DELIMITER = " - ";
private static final String OK_SUFFIX = COMMAND_DELIMITER + "OK";
private static final Map INSTANCES = new ConcurrentHashMap<>();
private static volatile ServerSocket mainLock;
private static volatile String lockUuid;
private volatile ServerHandler handler;
private final int portNumber;
private final long instanceWaitTimeout;
/**
* Internal supported communication commands. Should be exactly 6 characters long.
*/
enum Command {
UPDATE,
FINISH
}
private static class ServerHandler extends Thread {
private final Random random = new Random();
private final Queue workSockets = new LinkedList<>();
private volatile boolean running = true;
public ServerHandler() {
setDaemon(true);
setName("rp-launch-join");
}
@Override
public void run() {
while (running) {
try {
Socket s = mainLock.accept();
workSockets.add(s);
OutputStream os = s.getOutputStream();
byte[] launchUuid = lockUuid.getBytes(TRANSFER_CHARSET);
os.write(launchUuid);
os.flush();
byte[] updateUuid = new byte[(Command.UPDATE.name() + COMMAND_DELIMITER + lockUuid).getBytes(TRANSFER_CHARSET).length];
InputStream is = s.getInputStream();
//noinspection ResultOfMethodCallIgnored
is.read(updateUuid);
String data = new String(updateUuid, TRANSFER_CHARSET);
final Command command = Command.valueOf(data.substring(0, data.indexOf(COMMAND_DELIMITER)));
final String instanceUuid = data.substring(data.indexOf(COMMAND_DELIMITER) + COMMAND_DELIMITER.length());
switch (command) {
case UPDATE:
INSTANCES.put(instanceUuid, new Date());
break;
case FINISH:
INSTANCES.remove(instanceUuid);
break;
}
String answer = instanceUuid + OK_SUFFIX;
byte[] answerBuffer = answer.getBytes(TRANSFER_CHARSET);
os.write(answerBuffer);
os.flush();
if (random.nextInt(5) == 0) {
Collection checked = new LinkedList<>();
Socket current;
while ((current = workSockets.poll()) != null) {
if (!current.isClosed()) {
checked.add(current);
}
}
workSockets.addAll(checked);
}
} catch (IOException e) {
LOGGER.warn("Error serving server connections: ", e);
}
}
Socket current;
while ((current = workSockets.poll()) != null) {
if (!current.isClosed()) {
try {
current.close();
} catch (IOException e) {
LOGGER.warn("Unable to close socket properly", e);
}
}
}
}
}
public LaunchIdLockSocket(ListenerParameters listenerParameters) {
super(listenerParameters);
portNumber = listenerParameters.getLockPortNumber();
instanceWaitTimeout = listenerParameters.getLockWaitTimeout();
}
String sendCommand(@Nonnull final Command command, @Nonnull final String instanceUuid) {
String result = new Waiter("Wait for a socket connection").duration(instanceWaitTimeout, TimeUnit.MILLISECONDS)
.applyRandomDiscrepancy(MAX_WAIT_TIME_DISCREPANCY)
.pollingEvery(1, TimeUnit.SECONDS)
.till(() -> {
try (Socket socket = new Socket(InetAddress.getLocalHost(), portNumber)) {
byte[] launchAnswerBuffer = new byte[instanceUuid.getBytes(TRANSFER_CHARSET).length];
InputStream is = socket.getInputStream();
//noinspection ResultOfMethodCallIgnored
is.read(launchAnswerBuffer);
String launchUuid = new String(launchAnswerBuffer, TRANSFER_CHARSET);
byte[] saveBuffer = (command.name() + COMMAND_DELIMITER + instanceUuid).getBytes(TRANSFER_CHARSET);
OutputStream os = socket.getOutputStream();
os.write(saveBuffer);
os.flush();
String expectedAnswer = instanceUuid + OK_SUFFIX;
byte[] answerBuffer = new byte[expectedAnswer.getBytes(TRANSFER_CHARSET).length];
//noinspection ResultOfMethodCallIgnored
is.read(answerBuffer);
String answer = new String(answerBuffer, TRANSFER_CHARSET);
if (!expectedAnswer.equals(answer)) {
LOGGER.warn("Invalid server instance UUID '{}' answer", command.name());
return null;
}
return launchUuid;
} catch (IOException e) {
LOGGER.warn("Unable to '{}' instance UUID on port '{}', connection error", command.name(), portNumber, e);
return null;
}
});
return result == null ? instanceUuid : result;
}
private String executeCommand(@Nonnull final Command command, @Nonnull final String instanceUuid) {
if (mainLock != null) {
switch (command) {
case UPDATE:
INSTANCES.put(instanceUuid, new Date());
break;
case FINISH:
INSTANCES.remove(instanceUuid);
break;
}
return lockUuid;
}
return sendCommand(command, instanceUuid);
}
private String writeInstanceUuid(@Nonnull final String instanceUuid) {
return executeCommand(Command.UPDATE, instanceUuid);
}
/**
* Returns a Launch UUID for many Clients launched on one machine.
*
* @param uuid a Client instance UUID, which will be written to lock and sync files and, if it the first thread which managed to
* obtain lock on '.lock' file, returned to every client instance.
* @return either a Client instance UUID, either the first UUID which thread managed to place a lock on a '.lock' file.
*/
@Override
public String obtainLaunchUuid(@Nonnull final String uuid) {
Objects.requireNonNull(uuid);
if (mainLock == null) {
try {
synchronized (LaunchIdLockSocket.class) {
if (mainLock == null) {
mainLock = new ServerSocket(portNumber, SOCKET_BACKLOG, InetAddress.getLocalHost());
lockUuid = uuid;
INSTANCES.put(uuid, new Date());
}
}
if (uuid.equals(lockUuid)) {
// This is the main thread, serve clients
handler = new ServerHandler();
handler.start();
} else {
// Another thread acquired lock while synchronization wait
writeInstanceUuid(uuid);
}
return lockUuid;
} catch (IOException e) {
// already busy, try to connect
LOGGER.debug("Unable to obtain lock socket", e);
return writeInstanceUuid(uuid);
}
} else {
if (!uuid.equals(lockUuid)) {
writeInstanceUuid(uuid);
}
return lockUuid;
}
}
@Override
public void updateInstanceUuid(@Nonnull final String instanceUuid) {
writeInstanceUuid(instanceUuid);
}
void reset() {
if (handler != null) {
handler.running = false;
handler = null;
}
if (mainLock != null) {
ServerSocket socket = mainLock;
mainLock = null; // faster than closing connection
try {
socket.close();
} catch (IOException e) {
LOGGER.warn("Unable to close server socket properly", e);
}
}
lockUuid = null;
INSTANCES.clear();
}
/**
* Remove self UUID from sync file, means that a client finished its Launch. If this is the last UUID in the sync file, lock and sync
* files will be removed.
*
* @param instanceUuid a Client instance UUID.
*/
@Override
public void finishInstanceUuid(@Nonnull final String instanceUuid) {
executeCommand(Command.FINISH, instanceUuid);
if (mainLock != null) {
if (instanceUuid.equals(lockUuid)) {
synchronized (LaunchIdLockSocket.class) {
if (mainLock != null) {
reset();
}
}
}
}
}
@Nonnull
@Override
public Collection getLiveInstanceUuids() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MILLISECOND, -instanceWaitTimeout < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) -instanceWaitTimeout);
Date timeoutTime = calendar.getTime();
return INSTANCES.entrySet()
.stream()
.filter(e -> e.getValue().after(timeoutTime))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy