Skip to content

Commit 8e89050

Browse files
Enrico Olivellianmolnar
Enrico Olivelli
authored andcommitted
ZOOKEEPER-3620: Allow to override calls to System.exit in server side code
- Introduce a way to override calls to System.exit - enable DM_EXIT spotbugs rule see https://issues.apache.org/jira/browse/ZOOKEEPER-3620 for more context. Author: Enrico Olivelli <[email protected]> Author: Enrico Olivelli <[email protected]> Reviewers: [email protected] Closes apache#1147 from eolivelli/fix/ZOOKEEPER-3620-no-systemexit and squashes the following commits: a234f85 [Enrico Olivelli] Fix checkstyle 4c4fec4 [Enrico Olivelli] Fix spotbugs warning ae339b7 [Enrico Olivelli] Revert changes to VerGen.java 0e5ee07 [Enrico Olivelli] Enable DM_EXIT spotbugs rule for the full code base b05a4bf [Enrico Olivelli] ZOOKEEPER-3620 Allow to override calls to System.exit in server side code: - Use a common utility to call System.exit - Override calls to System.exit to a NO-OP function in tests
1 parent 4132b64 commit 8e89050

20 files changed

+139
-41
lines changed

excludeFindBugsFilter.xml

-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,5 @@
77
<!-- this problem is to be addressed in ZOOKEEPER-3227 -->
88
<Bug pattern="DM_DEFAULT_ENCODING"/>
99

10-
<!-- not really a problem -->
11-
<Bug pattern="DM_EXIT"/>
12-
1310
</FindBugsFilter>
1411

zookeeper-server/src/main/java/org/apache/zookeeper/Version.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2222
import org.apache.zookeeper.server.ExitCode;
23+
import org.apache.zookeeper.util.ServiceUtils;
2324

2425
public class Version implements org.apache.zookeeper.version.Info {
2526

@@ -48,7 +49,7 @@ public static void printUsage() {
4849
System.out.print("Usage:\tjava -cp ... org.apache.zookeeper.Version "
4950
+ "[--full | --short | --revision],\n\tPrints --full version "
5051
+ "info if no arg specified.");
51-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
52+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
5253
}
5354

5455
/**
@@ -68,7 +69,7 @@ public static void main(String[] args) {
6869
}
6970
if (args.length == 0 || (args.length == 1 && args[0].equals("--full"))) {
7071
System.out.println(getFullVersion());
71-
System.exit(ExitCode.EXECUTION_FINISHED.getValue());
72+
ServiceUtils.requestSystemExit(ExitCode.EXECUTION_FINISHED.getValue());
7273
}
7374
if (args[0].equals("--short")) {
7475
System.out.println(getVersion());
@@ -77,7 +78,7 @@ public static void main(String[] args) {
7778
} else {
7879
printUsage();
7980
}
80-
System.exit(ExitCode.EXECUTION_FINISHED.getValue());
81+
ServiceUtils.requestSystemExit(ExitCode.EXECUTION_FINISHED.getValue());
8182
}
8283

8384
}

zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.apache.zookeeper.cli.VersionCommand;
6767
import org.apache.zookeeper.client.ZKClientConfig;
6868
import org.apache.zookeeper.server.ExitCode;
69+
import org.apache.zookeeper.util.ServiceUtils;
6970
import org.slf4j.Logger;
7071
import org.slf4j.LoggerFactory;
7172

@@ -353,7 +354,7 @@ void run() throws CliException, IOException, InterruptedException {
353354
// Command line args non-null. Run what was passed.
354355
processCmd(cl);
355356
}
356-
System.exit(exitCode);
357+
ServiceUtils.requestSystemExit(exitCode);
357358
}
358359

359360
public void executeLine(String line) throws CliException, InterruptedException, IOException {
@@ -396,7 +397,7 @@ protected boolean processZKCmd(MyCommandOptions co) throws CliException, IOExcep
396397

397398
if (cmd.equals("quit")) {
398399
zk.close();
399-
System.exit(exitCode);
400+
ServiceUtils.requestSystemExit(exitCode);
400401
} else if (cmd.equals("redo") && args.length >= 2) {
401402
Integer i = Integer.decode(args[1]);
402403
if (commandCount <= i || i < 0) { // don't allow redoing this redo

zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import org.apache.zookeeper.txn.SetDataTxn;
7979
import org.apache.zookeeper.txn.Txn;
8080
import org.apache.zookeeper.txn.TxnHeader;
81+
import org.apache.zookeeper.util.ServiceUtils;
8182
import org.slf4j.Logger;
8283
import org.slf4j.LoggerFactory;
8384

@@ -301,7 +302,7 @@ public DataTree() {
301302
childWatches = WatchManagerFactory.createWatchManager();
302303
} catch (Exception e) {
303304
LOG.error("Unexpected exception when creating WatchManager, exiting abnormally", e);
304-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
305+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
305306
}
306307
}
307308

zookeeper-server/src/main/java/org/apache/zookeeper/server/LogFormatter.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.zookeeper.server.persistence.FileTxnLog;
3434
import org.apache.zookeeper.server.util.SerializeUtils;
3535
import org.apache.zookeeper.txn.TxnHeader;
36+
import org.apache.zookeeper.util.ServiceUtils;
3637
import org.slf4j.Logger;
3738
import org.slf4j.LoggerFactory;
3839

@@ -51,13 +52,13 @@ public class LogFormatter {
5152
public static void main(String[] args) throws Exception {
5253
if (args.length != 1) {
5354
System.err.println("USAGE: LogFormatter log_file");
54-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
55+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
5556
}
5657

5758
String error = ZKUtil.validateFileInput(args[0]);
5859
if (null != error) {
5960
System.err.println(error);
60-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
61+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
6162
}
6263

6364
FileInputStream fis = new FileInputStream(args[0]);
@@ -67,7 +68,7 @@ public static void main(String[] args) throws Exception {
6768

6869
if (fhdr.getMagic() != FileTxnLog.TXNLOG_MAGIC) {
6970
System.err.println("Invalid magic number for " + args[0]);
70-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
71+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
7172
}
7273
System.out.println("ZooKeeper Transactional Log File with dbid "
7374
+ fhdr.getDbid()

zookeeper-server/src/main/java/org/apache/zookeeper/server/PurgeTxnLog.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.yetus.audience.InterfaceAudience;
3131
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
3232
import org.apache.zookeeper.server.persistence.Util;
33+
import org.apache.zookeeper.util.ServiceUtils;
3334
import org.slf4j.Logger;
3435
import org.slf4j.LoggerFactory;
3536

@@ -230,7 +231,7 @@ private static int validateAndGetCount(String number) {
230231

231232
private static void printUsageThenExit() {
232233
printUsage();
233-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
234+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
234235
}
235236

236237
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/RequestThrottler.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
2222
import java.util.concurrent.LinkedBlockingQueue;
23+
import org.apache.zookeeper.util.ServiceUtils;
2324
import org.slf4j.Logger;
2425
import org.slf4j.LoggerFactory;
2526

@@ -257,7 +258,7 @@ public void shutdown() {
257258
} catch (InterruptedException e) {
258259
LOG.warn("Interrupted while waiting for {} to finish", this);
259260
//TODO apply ZOOKEEPER-575 and remove this line.
260-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
261+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
261262
}
262263
}
263264

zookeeper-server/src/main/java/org/apache/zookeeper/server/SnapshotFormatter.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.zookeeper.server.persistence.FileSnap;
3636
import org.apache.zookeeper.server.persistence.SnapStream;
3737
import org.apache.zookeeper.server.persistence.Util;
38+
import org.apache.zookeeper.util.ServiceUtils;
3839
import org.json.simple.JSONValue;
3940

4041
/**
@@ -72,18 +73,19 @@ public static void main(String[] args) throws Exception {
7273
System.err.println("USAGE: SnapshotFormatter [-d|-json] snapshot_file");
7374
System.err.println(" -d dump the data for each znode");
7475
System.err.println(" -json dump znode info in json format");
75-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
76+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
77+
return;
7678
}
7779

7880
String error = ZKUtil.validateFileInput(snapshotFile);
7981
if (null != error) {
8082
System.err.println(error);
81-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
83+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
8284
}
8385

8486
if (dumpData && dumpJson) {
8587
System.err.println("Cannot specify both data dump (-d) and json mode (-json) in same call");
86-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
88+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
8789
}
8890

8991
new SnapshotFormatter().run(snapshotFile, dumpData, dumpJson);

zookeeper-server/src/main/java/org/apache/zookeeper/server/TraceFormatter.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.text.DateFormat;
2626
import java.util.Date;
2727
import org.apache.zookeeper.ZooDefs.OpCode;
28+
import org.apache.zookeeper.util.ServiceUtils;
2829

2930
public class TraceFormatter {
3031

@@ -35,7 +36,7 @@ public class TraceFormatter {
3536
public static void main(String[] args) throws IOException {
3637
if (args.length != 1) {
3738
System.err.println("USAGE: TraceFormatter trace_file");
38-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
39+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
3940
}
4041
FileChannel fc = new FileInputStream(args[0]).getChannel();
4142
while (true) {

zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import org.apache.zookeeper.server.util.RequestPathMetricsCollector;
8181
import org.apache.zookeeper.txn.CreateSessionTxn;
8282
import org.apache.zookeeper.txn.TxnHeader;
83+
import org.apache.zookeeper.util.ServiceUtils;
8384
import org.slf4j.Logger;
8485
import org.slf4j.LoggerFactory;
8586

@@ -511,7 +512,7 @@ public void takeSnapshot(boolean syncSnap) {
511512
LOG.error("Severe unrecoverable error, exiting", e);
512513
// This is a severe error that we cannot recover from,
513514
// so we need to exit
514-
System.exit(ExitCode.TXNLOG_ERROR_TAKING_SNAPSHOT.getValue());
515+
ServiceUtils.requestSystemExit(ExitCode.TXNLOG_ERROR_TAKING_SNAPSHOT.getValue());
515516
}
516517
long elapsed = Time.currentElapsedTime() - start;
517518
LOG.info("Snapshot taken in {} ms", elapsed);

zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.zookeeper.server.persistence.FileTxnSnapLog.DatadirException;
3636
import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
3737
import org.apache.zookeeper.server.util.JvmPauseMonitor;
38+
import org.apache.zookeeper.util.ServiceUtils;
3839
import org.slf4j.Logger;
3940
import org.slf4j.LoggerFactory;
4041

@@ -69,29 +70,29 @@ public static void main(String[] args) {
6970
LOG.info(USAGE);
7071
System.err.println(USAGE);
7172
ZKAuditProvider.addServerStartFailureAuditLog();
72-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
73+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
7374
} catch (ConfigException e) {
7475
LOG.error("Invalid config, exiting abnormally", e);
7576
System.err.println("Invalid config, exiting abnormally");
7677
ZKAuditProvider.addServerStartFailureAuditLog();
77-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
78+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
7879
} catch (DatadirException e) {
7980
LOG.error("Unable to access datadir, exiting abnormally", e);
8081
System.err.println("Unable to access datadir, exiting abnormally");
8182
ZKAuditProvider.addServerStartFailureAuditLog();
82-
System.exit(ExitCode.UNABLE_TO_ACCESS_DATADIR.getValue());
83+
ServiceUtils.requestSystemExit(ExitCode.UNABLE_TO_ACCESS_DATADIR.getValue());
8384
} catch (AdminServerException e) {
8485
LOG.error("Unable to start AdminServer, exiting abnormally", e);
8586
System.err.println("Unable to start AdminServer, exiting abnormally");
8687
ZKAuditProvider.addServerStartFailureAuditLog();
87-
System.exit(ExitCode.ERROR_STARTING_ADMIN_SERVER.getValue());
88+
ServiceUtils.requestSystemExit(ExitCode.ERROR_STARTING_ADMIN_SERVER.getValue());
8889
} catch (Exception e) {
8990
LOG.error("Unexpected exception, exiting abnormally", e);
9091
ZKAuditProvider.addServerStartFailureAuditLog();
91-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
92+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
9293
}
9394
LOG.info("Exiting normally");
94-
System.exit(ExitCode.EXECUTION_FINISHED.getValue());
95+
ServiceUtils.requestSystemExit(ExitCode.EXECUTION_FINISHED.getValue());
9596
}
9697

9798
protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {

zookeeper-server/src/main/java/org/apache/zookeeper/server/persistence/TxnLogToolkit.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.apache.zookeeper.txn.SetDataTxn;
5959
import org.apache.zookeeper.txn.Txn;
6060
import org.apache.zookeeper.txn.TxnHeader;
61+
import org.apache.zookeeper.util.ServiceUtils;
6162

6263
public class TxnLogToolkit implements Closeable {
6364

@@ -126,7 +127,7 @@ public static void main(String[] args) throws Exception {
126127
printHelpAndExit(e.getExitCode(), e.getOptions());
127128
} catch (TxnLogToolkitException e) {
128129
System.err.println(e.getMessage());
129-
System.exit(e.getExitCode());
130+
ServiceUtils.requestSystemExit(e.getExitCode());
130131
}
131132
}
132133

@@ -424,7 +425,7 @@ private static TxnLogToolkit parseCommandLine(String[] args) throws TxnLogToolki
424425
private static void printHelpAndExit(int exitCode, Options options) {
425426
HelpFormatter help = new HelpFormatter();
426427
help.printHelp(120, "TxnLogToolkit [-dhrvc] <txn_log_file_name> (-z <zxid>)", "", options, "");
427-
System.exit(exitCode);
428+
ServiceUtils.requestSystemExit(exitCode);
428429
}
429430

430431
private void printStat() {

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/FollowerZooKeeperServer.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.zookeeper.server.ZKDatabase;
3535
import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
3636
import org.apache.zookeeper.txn.TxnHeader;
37+
import org.apache.zookeeper.util.ServiceUtils;
3738
import org.slf4j.Logger;
3839
import org.slf4j.LoggerFactory;
3940

@@ -100,7 +101,7 @@ public void commit(long zxid) {
100101
if (firstElementZxid != zxid) {
101102
LOG.error("Committing zxid 0x" + Long.toHexString(zxid)
102103
+ " but next pending txn 0x" + Long.toHexString(firstElementZxid));
103-
System.exit(ExitCode.UNMATCHED_TXN_COMMIT.getValue());
104+
ServiceUtils.requestSystemExit(ExitCode.UNMATCHED_TXN_COMMIT.getValue());
104105
}
105106
Request request = pendingTxns.remove();
106107
request.logLatency(ServerMetrics.getMetrics().COMMIT_PROPAGATION_LATENCY);

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/Learner.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.apache.zookeeper.server.util.ZxidUtils;
5353
import org.apache.zookeeper.txn.SetDataTxn;
5454
import org.apache.zookeeper.txn.TxnHeader;
55+
import org.apache.zookeeper.util.ServiceUtils;
5556
import org.slf4j.Logger;
5657
import org.slf4j.LoggerFactory;
5758

@@ -455,14 +456,13 @@ protected void syncWithLeader(long newLeaderZxid) throws Exception {
455456
if (!truncated) {
456457
// not able to truncate the log
457458
LOG.error("Not able to truncate the log 0x{}", Long.toHexString(qp.getZxid()));
458-
System.exit(ExitCode.QUORUM_PACKET_ERROR.getValue());
459+
ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
459460
}
460461
zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
461462

462463
} else {
463464
LOG.error("Got unexpected packet from leader: {}, exiting ... ", LearnerHandler.packetToString(qp));
464-
System.exit(ExitCode.QUORUM_PACKET_ERROR.getValue());
465-
465+
ServiceUtils.requestSystemExit(ExitCode.QUORUM_PACKET_ERROR.getValue());
466466
}
467467
zk.getZKDatabase().initConfigInZKDatabase(self.getQuorumVerifier());
468468
zk.createSessionTracker();

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumCnxManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
5656
import org.apache.zookeeper.server.util.ConfigUtils;
5757
import org.apache.zookeeper.util.CircularBlockingQueue;
58+
import org.apache.zookeeper.util.ServiceUtils;
5859
import org.slf4j.Logger;
5960
import org.slf4j.LoggerFactory;
6061

@@ -825,7 +826,7 @@ public class Listener extends ZooKeeperThread {
825826
private static final int DEFAULT_PORT_BIND_MAX_RETRY = 3;
826827

827828
private final int portBindMaxRetry;
828-
private Runnable socketBindErrorHandler = () -> System.exit(ExitCode.UNABLE_TO_BIND_QUORUM_PORT.getValue());
829+
private Runnable socketBindErrorHandler = () -> ServiceUtils.requestSystemExit(ExitCode.UNABLE_TO_BIND_QUORUM_PORT.getValue());
829830
volatile ServerSocket ss = null;
830831

831832
public Listener() {

zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.zookeeper.server.persistence.FileTxnSnapLog.DatadirException;
3939
import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
4040
import org.apache.zookeeper.server.util.JvmPauseMonitor;
41+
import org.apache.zookeeper.util.ServiceUtils;
4142
import org.slf4j.Logger;
4243
import org.slf4j.LoggerFactory;
4344

@@ -92,29 +93,29 @@ public static void main(String[] args) {
9293
LOG.info(USAGE);
9394
System.err.println(USAGE);
9495
ZKAuditProvider.addServerStartFailureAuditLog();
95-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
96+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
9697
} catch (ConfigException e) {
9798
LOG.error("Invalid config, exiting abnormally", e);
9899
System.err.println("Invalid config, exiting abnormally");
99100
ZKAuditProvider.addServerStartFailureAuditLog();
100-
System.exit(ExitCode.INVALID_INVOCATION.getValue());
101+
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
101102
} catch (DatadirException e) {
102103
LOG.error("Unable to access datadir, exiting abnormally", e);
103104
System.err.println("Unable to access datadir, exiting abnormally");
104105
ZKAuditProvider.addServerStartFailureAuditLog();
105-
System.exit(ExitCode.UNABLE_TO_ACCESS_DATADIR.getValue());
106+
ServiceUtils.requestSystemExit(ExitCode.UNABLE_TO_ACCESS_DATADIR.getValue());
106107
} catch (AdminServerException e) {
107108
LOG.error("Unable to start AdminServer, exiting abnormally", e);
108109
System.err.println("Unable to start AdminServer, exiting abnormally");
109110
ZKAuditProvider.addServerStartFailureAuditLog();
110-
System.exit(ExitCode.ERROR_STARTING_ADMIN_SERVER.getValue());
111+
ServiceUtils.requestSystemExit(ExitCode.ERROR_STARTING_ADMIN_SERVER.getValue());
111112
} catch (Exception e) {
112113
LOG.error("Unexpected exception, exiting abnormally", e);
113114
ZKAuditProvider.addServerStartFailureAuditLog();
114-
System.exit(ExitCode.UNEXPECTED_ERROR.getValue());
115+
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
115116
}
116117
LOG.info("Exiting normally");
117-
System.exit(ExitCode.EXECUTION_FINISHED.getValue());
118+
ServiceUtils.requestSystemExit(ExitCode.EXECUTION_FINISHED.getValue());
118119
}
119120

120121
protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {

0 commit comments

Comments
 (0)