make JNA optional for tests and move classes to bootstrap package

Today, JNA is a optional dependency in the build but when running tests or running
with mlockall set to true, JNA must be on the classpath for Windows systems since
we always try to load JNA classes when using mlockall.

The old Natives class was renamed to JNANatives, and a new Natives class is
introduced without any direct imports on JNA classes. The Natives class checks to
see if JNA classes are available at startup. If the classes are available the Natives
class will delegate to the JNANatives class. If the classes are not available the
Natives class will not use the JNANatives class, which results in no additional attempts
to load JNA classes.

Additionally, all of the JNA classes were moved to the bootstrap package and made
package private as this is the only place they should be called from.

Closes #11360
This commit is contained in:
jaymode 2015-05-27 09:14:01 -04:00
parent 95faa35853
commit e54dd688a1
14 changed files with 176 additions and 97 deletions

View File

@ -28,8 +28,6 @@ import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.CreationException;
import org.elasticsearch.common.inject.spi.Message;
import org.elasticsearch.common.jna.Kernel32Library;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.lease.Releasables;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
@ -48,7 +46,6 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import static com.google.common.collect.Sets.newHashSet;
import static org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
/**
@ -122,7 +119,7 @@ public class Bootstrap {
// force remainder of JNA to be loaded (if available).
try {
Kernel32Library.getInstance();
JNAKernel32Library.getInstance();
} catch (Throwable ignored) {
// we've already logged this.
}
@ -143,6 +140,10 @@ public class Bootstrap {
StringHelper.randomId();
}
public static boolean isMemoryLocked() {
return Natives.isMemoryLocked();
}
private void setup(boolean addShutdownHook, Settings settings, Environment environment) throws Exception {
initializeNatives(settings.getAsBoolean("bootstrap.mlockall", false),
settings.getAsBoolean("bootstrap.ctrlhandler", true),

View File

@ -17,19 +17,17 @@
* under the License.
*/
package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;
import com.sun.jna.IntegerType;
import com.sun.jna.Native;
public interface ConsoleCtrlHandler {
public class SizeT extends IntegerType {
public SizeT() {
this(0);
}
public SizeT(long value) {
super(Native.SIZE_T_SIZE, value);
}
int CTRL_CLOSE_EVENT = 2;
/**
* Handles the Ctrl event.
*
* @param code the code corresponding to the Ctrl sent.
* @return true if the handler processed the event, false otherwise. If false, the next handler will be called.
*/
boolean handle(int code);
}

View File

@ -17,7 +17,7 @@
* under the License.
*/
package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;
import com.sun.jna.Native;
import org.elasticsearch.common.logging.ESLogger;
@ -27,9 +27,9 @@ import org.elasticsearch.common.logging.Loggers;
/**
*
*/
public class CLibrary {
class JNACLibrary {
private static ESLogger logger = Loggers.getLogger(CLibrary.class);
private static final ESLogger logger = Loggers.getLogger(JNACLibrary.class);
public static final int MCL_CURRENT = 1;
public static final int MCL_FUTURE = 2;
@ -39,17 +39,15 @@ public class CLibrary {
static {
try {
Native.register("c");
} catch (NoClassDefFoundError e) {
logger.warn("JNA not found. native methods (mlockall) will be disabled.");
} catch (UnsatisfiedLinkError e) {
logger.warn("unable to link C library. native methods (mlockall) will be disabled.");
}
}
public static native int mlockall(int flags);
static native int mlockall(int flags);
public static native int geteuid();
static native int geteuid();
private CLibrary() {
private JNACLibrary() {
}
}

View File

@ -17,7 +17,7 @@
* under the License.
*/
package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;
import com.google.common.collect.ImmutableList;
import com.sun.jna.*;
@ -35,9 +35,9 @@ import java.util.List;
/**
* Library for Windows/Kernel32
*/
public class Kernel32Library {
class JNAKernel32Library {
private static ESLogger logger = Loggers.getLogger(Kernel32Library.class);
private static final ESLogger logger = Loggers.getLogger(JNAKernel32Library.class);
// Callbacks must be kept around in order to be able to be called later,
// when the Windows ConsoleCtrlHandler sends an event.
@ -45,10 +45,10 @@ public class Kernel32Library {
// Native library instance must be kept around for the same reason.
private final static class Holder {
private final static Kernel32Library instance = new Kernel32Library();
private final static JNAKernel32Library instance = new JNAKernel32Library();
}
private Kernel32Library() {
private JNAKernel32Library() {
if (Constants.WINDOWS) {
try {
Native.register("kernel32");
@ -61,7 +61,7 @@ public class Kernel32Library {
}
}
public static Kernel32Library getInstance() {
static JNAKernel32Library getInstance() {
return Holder.instance;
}
@ -73,7 +73,7 @@ public class Kernel32Library {
* @throws java.lang.UnsatisfiedLinkError if the Kernel32 library is not loaded or if the native function is not found
* @throws java.lang.NoClassDefFoundError if the library for native calls is missing
*/
public boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
boolean addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
boolean result = false;
if (handler != null) {
NativeHandlerCallback callback = new NativeHandlerCallback(handler);
@ -85,7 +85,7 @@ public class Kernel32Library {
return result;
}
public ImmutableList<Object> getCallbacks() {
ImmutableList<Object> getCallbacks() {
return ImmutableList.builder().addAll(callbacks).build();
}
@ -98,7 +98,7 @@ public class Kernel32Library {
* @throws java.lang.UnsatisfiedLinkError if the Kernel32 library is not loaded or if the native function is not found
* @throws java.lang.NoClassDefFoundError if the library for native calls is missing
*/
public native boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);
native boolean SetConsoleCtrlHandler(StdCallLibrary.StdCallCallback handler, boolean add);
/**
* Handles consoles event with WIN API
@ -123,20 +123,6 @@ public class Kernel32Library {
}
}
public interface ConsoleCtrlHandler {
public static final int CTRL_CLOSE_EVENT = 2;
/**
* Handles the Ctrl event.
*
* @param code the code corresponding to the Ctrl sent.
* @return true if the handler processed the event, false otherwise. If false, the next handler will be called.
*/
boolean handle(int code);
}
/**
* Memory protection constraints
*
@ -167,6 +153,18 @@ public class Kernel32Library {
}
}
public static class SizeT extends IntegerType {
public SizeT() {
this(0);
}
public SizeT(long value) {
super(Native.SIZE_T_SIZE, value);
}
}
/**
* Locks the specified region of the process's virtual address space into physical
* memory, ensuring that subsequent access to the region will not incur a page fault.
@ -177,7 +175,7 @@ public class Kernel32Library {
* @param size The size of the region to be locked, in bytes.
* @return true if the function succeeds
*/
public native boolean VirtualLock(Pointer address, SizeT size);
native boolean VirtualLock(Pointer address, SizeT size);
/**
* Retrieves information about a range of pages within the virtual address space of a specified process.
@ -190,7 +188,7 @@ public class Kernel32Library {
* @param length The size of the buffer pointed to by the memoryInfo parameter, in bytes.
* @return the actual number of bytes returned in the information buffer.
*/
public native int VirtualQueryEx(Pointer handle, Pointer address, MemoryBasicInformation memoryInfo, int length);
native int VirtualQueryEx(Pointer handle, Pointer address, MemoryBasicInformation memoryInfo, int length);
/**
* Sets the minimum and maximum working set sizes for the specified process.
@ -202,7 +200,7 @@ public class Kernel32Library {
* @param maxSize The maximum working set size for the process, in bytes.
* @return true if the function succeeds.
*/
public native boolean SetProcessWorkingSetSize(Pointer handle, SizeT minSize, SizeT maxSize);
native boolean SetProcessWorkingSetSize(Pointer handle, SizeT minSize, SizeT maxSize);
/**
* Retrieves a pseudo handle for the current process.
@ -211,7 +209,7 @@ public class Kernel32Library {
*
* @return a pseudo handle to the current process.
*/
public native Pointer GetCurrentProcess();
native Pointer GetCurrentProcess();
/**
* Closes an open object handle.
@ -221,5 +219,5 @@ public class Kernel32Library {
* @param handle A valid handle to an open object.
* @return true if the function succeeds.
*/
public native boolean CloseHandle(Pointer handle);
native boolean CloseHandle(Pointer handle);
}

View File

@ -17,32 +17,34 @@
* under the License.
*/
package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import org.apache.lucene.util.Constants;
import org.elasticsearch.common.jna.Kernel32Library.ConsoleCtrlHandler;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.monitor.jvm.JvmInfo;
import java.util.Locale;
/**
*
*/
public class Natives {
import static org.elasticsearch.bootstrap.JNAKernel32Library.SizeT;
/**
* This class performs the actual work with JNA and library bindings to call native methods. It should only be used after
* we are sure that the JNA classes are available to the JVM
*/
class JNANatives {
private static final ESLogger logger = Loggers.getLogger(JNANatives.class);
private static ESLogger logger = Loggers.getLogger(Natives.class);
// Set to true, in case native mlockall call was successful
public static boolean LOCAL_MLOCKALL = false;
public static void tryMlockall() {
static void tryMlockall() {
int errno = Integer.MIN_VALUE;
try {
int result = CLibrary.mlockall(CLibrary.MCL_CURRENT);
int result = JNACLibrary.mlockall(JNACLibrary.MCL_CURRENT);
if (result != 0) {
errno = Native.getLastError();
} else {
@ -54,7 +56,7 @@ public class Natives {
}
if (errno != Integer.MIN_VALUE) {
if (errno == CLibrary.ENOMEM && System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux")) {
if (errno == JNACLibrary.ENOMEM && System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux")) {
logger.warn("Unable to lock JVM memory (ENOMEM)."
+ " This can result in part of the JVM being swapped out."
+ " Increase RLIMIT_MEMLOCK (ulimit).");
@ -66,21 +68,20 @@ public class Natives {
}
/** Returns true if user is root, false if not, or if we don't know */
public static boolean definitelyRunningAsRoot() {
static boolean definitelyRunningAsRoot() {
if (Constants.WINDOWS) {
return false; // don't know
}
try {
return CLibrary.geteuid() == 0;
return JNACLibrary.geteuid() == 0;
} catch (UnsatisfiedLinkError e) {
// this will have already been logged by Kernel32Library, no need to repeat it
return false;
}
}
public static void tryVirtualLock()
{
Kernel32Library kernel = Kernel32Library.getInstance();
static void tryVirtualLock() {
JNAKernel32Library kernel = JNAKernel32Library.getInstance();
Pointer process = null;
try {
process = kernel.GetCurrentProcess();
@ -91,12 +92,12 @@ public class Natives {
if (!kernel.SetProcessWorkingSetSize(process, size, size)) {
logger.warn("Unable to lock JVM memory. Failed to set working set size. Error code " + Native.getLastError());
} else {
Kernel32Library.MemoryBasicInformation memInfo = new Kernel32Library.MemoryBasicInformation();
JNAKernel32Library.MemoryBasicInformation memInfo = new JNAKernel32Library.MemoryBasicInformation();
long address = 0;
while (kernel.VirtualQueryEx(process, new Pointer(address), memInfo, memInfo.size()) != 0) {
boolean lockable = memInfo.State.longValue() == Kernel32Library.MEM_COMMIT
&& (memInfo.Protect.longValue() & Kernel32Library.PAGE_NOACCESS) != Kernel32Library.PAGE_NOACCESS
&& (memInfo.Protect.longValue() & Kernel32Library.PAGE_GUARD) != Kernel32Library.PAGE_GUARD;
boolean lockable = memInfo.State.longValue() == JNAKernel32Library.MEM_COMMIT
&& (memInfo.Protect.longValue() & JNAKernel32Library.PAGE_NOACCESS) != JNAKernel32Library.PAGE_NOACCESS
&& (memInfo.Protect.longValue() & JNAKernel32Library.PAGE_GUARD) != JNAKernel32Library.PAGE_GUARD;
if (lockable) {
kernel.VirtualLock(memInfo.BaseAddress, new SizeT(memInfo.RegionSize.longValue()));
}
@ -114,18 +115,16 @@ public class Natives {
}
}
public static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
// The console Ctrl handler is necessary on Windows platforms only.
if (Constants.WINDOWS) {
try {
boolean result = Kernel32Library.getInstance().addConsoleCtrlHandler(handler);
boolean result = JNAKernel32Library.getInstance().addConsoleCtrlHandler(handler);
if (result) {
logger.debug("console ctrl handler correctly set");
} else {
logger.warn("unknown error " + Native.getLastError() + " when adding console ctrl handler:");
}
} catch (NoClassDefFoundError e) {
logger.warn("JNA not found: native methods and handlers will be disabled.");
} catch (UnsatisfiedLinkError e) {
// this will have already been logged by Kernel32Library, no need to repeat it
}

View File

@ -0,0 +1,84 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.bootstrap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
/**
* The Natives class is a wrapper class that checks if the classes necessary for calling native methods are available on
* startup. If they are not available, this class will avoid calling code that loads these classes.
*/
class Natives {
private static final ESLogger logger = Loggers.getLogger(Natives.class);
// marker to determine if the JNA class files are available to the JVM
private static boolean jnaAvailable = false;
static {
try {
// load one of the main JNA classes to see if the classes are available. this does not ensure that native
// libraries are available
Class.forName("com.sun.jna.Native");
jnaAvailable = true;
} catch(ClassNotFoundException e) {
logger.warn("JNA not found. native methods will be disabled.");
}
}
static void tryMlockall() {
if (!jnaAvailable) {
logger.warn("cannot mlockall because JNA is not available");
return;
}
JNANatives.tryMlockall();
}
static boolean definitelyRunningAsRoot() {
if (!jnaAvailable) {
logger.warn("cannot check if running as root because JNA is not available");
return false;
}
return JNANatives.definitelyRunningAsRoot();
}
static void tryVirtualLock() {
if (!jnaAvailable) {
logger.warn("cannot mlockall because JNA is not available");
return;
}
JNANatives.tryVirtualLock();
}
static void addConsoleCtrlHandler(ConsoleCtrlHandler handler) {
if (!jnaAvailable) {
logger.warn("cannot register console handler because JNA is not available");
return;
}
JNANatives.addConsoleCtrlHandler(handler);
}
static boolean isMemoryLocked() {
if (!jnaAvailable) {
return false;
}
return JNANatives.LOCAL_MLOCKALL;
}
}

View File

@ -19,10 +19,10 @@
package org.elasticsearch.monitor.process;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
@ -50,7 +50,7 @@ public class ProcessInfo implements Streamable, Serializable, ToXContent {
public ProcessInfo(long id, long maxFileDescriptors) {
this.id = id;
this.maxFileDescriptors = maxFileDescriptors;
this.mlockall = Natives.LOCAL_MLOCKALL;
this.mlockall = Bootstrap.isMemoryLocked();
}
public long refreshInterval() {

View File

@ -21,8 +21,8 @@ package org.elasticsearch.benchmark.mapping;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -85,8 +85,9 @@ public class ManyMappingsBenchmark {
public static void main(String[] args) throws Exception {
System.setProperty("es.logger.prefix", "");
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Settings settings = settingsBuilder()
.put("")
.put(SETTING_NUMBER_OF_SHARDS, 5)
.put(SETTING_NUMBER_OF_REPLICAS, 0)
.build();

View File

@ -20,10 +20,10 @@ package org.elasticsearch.benchmark.recovery;
import org.elasticsearch.action.admin.indices.recovery.RecoveryResponse;
import org.elasticsearch.action.admin.indices.recovery.ShardRecoveryResponse;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.routing.allocation.decider.DiskThresholdDecider;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings;
@ -57,7 +57,7 @@ public class ReplicaRecoveryBenchmark {
public static void main(String[] args) throws Exception {
System.setProperty("es.logger.prefix", "");
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Settings settings = settingsBuilder()
.put("gateway.type", "local")

View File

@ -26,8 +26,8 @@ import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.benchmark.search.aggregations.TermsAggregationSearchBenchmark.StatsResult;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.SizeValue;
@ -66,7 +66,7 @@ public class GlobalOrdinalsBenchmark {
public static void main(String[] args) throws Exception {
System.setProperty("es.logger.prefix", "");
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Random random = new Random();
Settings settings = settingsBuilder()

View File

@ -27,10 +27,10 @@ import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.StopWatch;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.SizeValue;
@ -71,7 +71,7 @@ public class SubAggregationSearchCollectModeBenchmark {
static Node[] nodes;
public static void main(String[] args) throws Exception {
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Random random = new Random();
Settings settings = settingsBuilder()

View File

@ -26,9 +26,9 @@ import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.SizeValue;
@ -71,7 +71,7 @@ public class TermsAggregationSearchAndIndexingBenchmark {
static Node[] nodes;
public static void main(String[] args) throws Exception {
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Settings settings = settingsBuilder()
.put("refresh_interval", "-1")
.put(SETTING_NUMBER_OF_SHARDS, 1)

View File

@ -28,10 +28,10 @@ import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.common.StopWatch;
import org.elasticsearch.common.jna.Natives;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.SizeValue;
@ -99,7 +99,7 @@ public class TermsAggregationSearchBenchmark {
}
public static void main(String[] args) throws Exception {
Natives.tryMlockall();
Bootstrap.initializeNatives(true, false, false);
Random random = new Random();
Settings settings = settingsBuilder()

View File

@ -17,7 +17,7 @@
* under the License.
*/
package org.elasticsearch.common.jna;
package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants;
import org.elasticsearch.test.ElasticsearchTestCase;
@ -25,23 +25,23 @@ import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
public class NativesTests extends ElasticsearchTestCase {
public class JNANativesTests extends ElasticsearchTestCase {
@Test
public void testMlockall() {
if (Constants.MAC_OS_X) {
assertFalse("Memory locking is not available on OS X platforms", Natives.LOCAL_MLOCKALL);
assertFalse("Memory locking is not available on OS X platforms", JNANatives.LOCAL_MLOCKALL);
}
}
@Test
public void testConsoleCtrlHandler() {
if (Constants.WINDOWS) {
assertNotNull(Kernel32Library.getInstance());
assertThat(Kernel32Library.getInstance().getCallbacks().size(), equalTo(1));
assertNotNull(JNAKernel32Library.getInstance());
assertThat(JNAKernel32Library.getInstance().getCallbacks().size(), equalTo(1));
} else {
assertNotNull(Kernel32Library.getInstance());
assertThat(Kernel32Library.getInstance().getCallbacks().size(), equalTo(0));
assertNotNull(JNAKernel32Library.getInstance());
assertThat(JNAKernel32Library.getInstance().getCallbacks().size(), equalTo(0));
}
}
}