1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.linecorp.centraldogma.server;
17
18 import static com.google.common.base.MoreObjects.firstNonNull;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.nio.file.AtomicMoveNotSupportedException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.StandardCopyOption;
26
27 import org.jspecify.annotations.Nullable;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import com.beust.jcommander.JCommander;
32 import com.beust.jcommander.Parameter;
33 import com.beust.jcommander.converters.FileConverter;
34
35 import com.linecorp.armeria.common.util.SystemInfo;
36
37
38
39
40 public final class Main {
41
42 private static final Logger logger = LoggerFactory.getLogger(Main.class);
43
44 static {
45 try {
46
47 final Class<?> bridgeHandler =
48 Class.forName("org.slf4j.bridge.SLF4JBridgeHandler", true, Main.class.getClassLoader());
49 bridgeHandler.getMethod("removeHandlersForRootLogger").invoke(null);
50 bridgeHandler.getMethod("install").invoke(null);
51 logger.debug("Installed the java.util.logging-to-SLF4J bridge.");
52 } catch (Throwable cause) {
53 logger.debug("Failed to install the java.util.logging-to-SLF4J bridge:", cause);
54 }
55 }
56
57 enum State {
58 NONE,
59 INITIALIZED,
60 STARTED,
61 STOPPED,
62 DESTROYED
63 }
64
65 private static final File DEFAULT_DATA_DIR =
66 new File(System.getProperty("user.dir", ".") + File.separatorChar + "data");
67
68 private static final File DEFAULT_CONFIG_FILE =
69 new File(System.getProperty("user.dir", ".") +
70 File.separatorChar + "conf" +
71 File.separatorChar + "dogma.json");
72
73 private static final File DEFAULT_PID_FILE =
74 new File(System.getProperty("user.dir", ".") +
75 File.separatorChar + "dogma.pid");
76
77 @Nullable
78 @Parameter(names = "-config", description = "The path to the config file", converter = FileConverter.class)
79 private File configFile;
80
81 @Nullable
82 @Parameter(names = "-pidfile",
83 description = "The path to the file containing the pid of the server" +
84 " (defaults to ./dogma.pid)",
85 converter = FileConverter.class)
86 private File pidFile;
87
88
89
90
91
92 @Nullable
93 @Parameter(names = { "-help", "-h" }, description = "Prints the usage", help = true)
94 private Boolean help;
95
96 private State state = State.NONE;
97 @Nullable
98 private CentralDogma dogma;
99 @Nullable
100 private PidFile procIdFile;
101 private boolean procIdFileCreated;
102
103 private Main(String[] args) {
104 final JCommander commander = new JCommander(this);
105 commander.setProgramName(getClass().getName());
106 commander.parse(args);
107
108 if (help != null && help) {
109 commander.usage();
110 } else {
111 procIdFile = new PidFile(firstNonNull(pidFile, DEFAULT_PID_FILE));
112 state = State.INITIALIZED;
113 }
114 }
115
116 synchronized void start() throws Exception {
117 switch (state) {
118 case NONE:
119 throw new IllegalStateException("not initialized");
120 case STARTED:
121 throw new IllegalStateException("started already");
122 case DESTROYED:
123 throw new IllegalStateException("can't start after destruction");
124 default:
125 break;
126 }
127
128 final File configFile = findConfigFile(this.configFile, DEFAULT_CONFIG_FILE);
129
130 final CentralDogma dogma;
131 if (configFile == null) {
132 dogma = new CentralDogmaBuilder(DEFAULT_DATA_DIR).build();
133 } else {
134 dogma = CentralDogma.forConfig(configFile);
135 }
136
137 dogma.start().get();
138
139 this.dogma = dogma;
140 state = State.STARTED;
141
142
143
144 assert procIdFile != null;
145 procIdFile.create();
146 procIdFileCreated = true;
147 }
148
149 @Nullable
150 private static File findConfigFile(@Nullable File file, File defaultFile) {
151 if (file != null) {
152 if (file.isFile() && file.canRead()) {
153 return file;
154 } else {
155 throw new IllegalStateException("cannot access the specified config file: " + file);
156 }
157 }
158
159
160 if (defaultFile.isFile() && defaultFile.canRead()) {
161 return defaultFile;
162 }
163 return null;
164 }
165
166 synchronized void stop() throws Exception {
167 switch (state) {
168 case NONE:
169 case INITIALIZED:
170 case STOPPED:
171 return;
172 case DESTROYED:
173 throw new IllegalStateException("can't stop after destruction");
174 }
175
176 final CentralDogma dogma = this.dogma;
177 assert dogma != null;
178 this.dogma = null;
179 dogma.stop().get();
180
181 state = State.STOPPED;
182 }
183
184 void destroy() {
185 switch (state) {
186 case NONE:
187 return;
188 case STARTED:
189 throw new IllegalStateException("can't destroy while running");
190 case DESTROYED:
191 return;
192 }
193
194 assert procIdFile != null;
195 if (procIdFileCreated) {
196 try {
197 procIdFile.destroy();
198 } catch (IOException e) {
199 logger.warn("Failed to destroy the PID file:", e);
200 }
201 }
202
203 state = State.DESTROYED;
204 }
205
206
207
208
209 public static void main(String[] args) throws Exception {
210 final Main main = new Main(args);
211
212
213 Runtime.getRuntime().addShutdownHook(new Thread("Central Dogma shutdown hook") {
214 @Override
215 public void run() {
216 try {
217 main.stop();
218 } catch (Exception e) {
219 logger.warn("Failed to stop the Central Dogma:", e);
220 }
221
222 try {
223 main.destroy();
224 } catch (Exception e) {
225 logger.warn("Failed to destroy the Central Dogma:", e);
226 }
227 }
228 });
229
230
231 if (main.state != State.INITIALIZED) {
232 System.exit(1);
233 return;
234 }
235
236 try {
237 main.start();
238 } catch (Throwable cause) {
239 logger.error("Failed to start the Central Dogma:", cause);
240
241 System.exit(1);
242 }
243 }
244
245
246
247
248 static final class PidFile {
249
250 private final File file;
251
252 private PidFile(File file) {
253 this.file = file;
254 }
255
256 void create() throws IOException {
257 if (file.exists()) {
258 throw new IllegalStateException("Failed to create a PID file. A file already exists: " +
259 file.getPath());
260 }
261
262 final int pid = SystemInfo.pid();
263 final Path temp = Files.createTempFile("central-dogma", ".tmp");
264 Files.write(temp, Integer.toString(pid).getBytes());
265 try {
266 Files.move(temp, file.toPath(), StandardCopyOption.ATOMIC_MOVE);
267 } catch (AtomicMoveNotSupportedException e) {
268 Files.move(temp, file.toPath());
269 }
270
271 logger.debug("A PID file has been created: {}", file);
272 }
273
274 void destroy() throws IOException {
275 if (Files.deleteIfExists(file.toPath())) {
276 logger.debug("Successfully deleted the PID file: {}", file);
277 }
278 }
279 }
280 }