001/* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2014 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.process; 021 022import org.apache.commons.io.IOUtils; 023import org.slf4j.LoggerFactory; 024 025import java.io.File; 026import java.io.IOException; 027import java.io.RandomAccessFile; 028import java.nio.MappedByteBuffer; 029import java.nio.channels.FileChannel; 030 031/** 032 * Process inter-communication to : 033 * <ul> 034 * <li>share status of child process</li> 035 * <li>stop child process</li> 036 * </ul> 037 * 038 * <p/> 039 * It relies on files shared by both processes. Following alternatives were considered but not selected : 040 * <ul> 041 * <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li> 042 * <li>simple socket protocol: same drawbacks are RMI connection</li> 043 * <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li> 044 * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li> 045 * </ul> 046 */ 047public class ProcessCommands { 048 049 /** 050 * The ByteBuffer will contains : 051 * <ul> 052 * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li> 053 * <li>Then each 10 bytes will be reserved for each process</li> 054 * </ul> 055 * 056 * Description of ten bytes of each process : 057 * <ul> 058 * <li>First byte will contains the state 0x00 until READY 0x01</li> 059 * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li> 060 * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li> 061 * </ul> 062 */ 063 final MappedByteBuffer mappedByteBuffer; 064 private final RandomAccessFile sharedMemory; 065 private static final int MAX_PROCESSES = 50; 066 private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 8; 067 068 // With this shared memory we can handle up to MAX_PROCESSES processes 069 private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES; 070 071 public static final byte STOP = (byte) 0xFF; 072 public static final byte READY = (byte) 0x01; 073 public static final byte EMPTY = (byte) 0x00; 074 075 private int processNumber; 076 077 public ProcessCommands(File directory, int processNumber) { 078 // processNumber should not excess MAX_PROCESSES and must not be below -1 079 assert processNumber <= MAX_PROCESSES : "Incorrect process number"; 080 assert processNumber >= -1 : "Incorrect process number"; 081 082 this.processNumber = processNumber; 083 if (!directory.isDirectory() || !directory.exists()) { 084 throw new IllegalArgumentException("Not a valid directory: " + directory); 085 } 086 087 try { 088 sharedMemory = new RandomAccessFile(new File(directory, "sharedmemory"), "rw"); 089 mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, MAX_SHARED_MEMORY); 090 cleanData(); 091 } catch (IOException e) { 092 throw new IllegalArgumentException("Unable to create shared memory : ", e); 093 } 094 } 095 096 public boolean isReady() { 097 return canBeMonitored() && mappedByteBuffer.get(offset()) == READY; 098 } 099 100 /** 101 * To be executed by child process to declare that it's ready 102 */ 103 public void setReady() { 104 if (canBeMonitored()) { 105 mappedByteBuffer.put(offset(), READY); 106 } 107 } 108 109 public void ping() { 110 if (canBeMonitored()) { 111 mappedByteBuffer.putLong(2 + offset(), System.currentTimeMillis()); 112 } 113 } 114 115 public long getLastPing() { 116 if (canBeMonitored()) { 117 return mappedByteBuffer.getLong(2 + offset()); 118 } else { 119 return -1; 120 } 121 } 122 123 /** 124 * To be executed by monitor process to ask for child process termination 125 */ 126 public void askForStop() { 127 mappedByteBuffer.put(offset() + 1, STOP); 128 } 129 130 public boolean askedForStop() { 131 return mappedByteBuffer.get(offset() + 1) == STOP; 132 } 133 134 public void endWatch() { 135 IOUtils.closeQuietly(sharedMemory); 136 } 137 138 int offset() { 139 return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber; 140 } 141 142 private boolean canBeMonitored() { 143 boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES; 144 if (!result) { 145 LoggerFactory.getLogger(getClass()).info("This process cannot be monitored. Process Id : [{}]", processNumber); 146 } 147 return result; 148 } 149 150 private void cleanData() { 151 for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) { 152 mappedByteBuffer.put(offset() + i, EMPTY); 153 } 154 } 155 156 public static int getMaxProcesses() { 157 return MAX_PROCESSES; 158 } 159}