diff --git a/apps/android_rpc/app/src/main/AndroidManifest.xml b/apps/android_rpc/app/src/main/AndroidManifest.xml index 6b0d6d995dba47956b26ac3e69a698b312af7938..d8385e3b15e2508051c74d806156a3bb49eb1926 100644 --- a/apps/android_rpc/app/src/main/AndroidManifest.xml +++ b/apps/android_rpc/app/src/main/AndroidManifest.xml @@ -2,11 +2,14 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ml.dmlc.tvm.tvmrpc" > + <uses-permission android:name="android.permission.INTERNET" /> + <application android:allowBackup="true" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme" > + android:theme="@style/AppTheme" + android:icon="@mipmap/ic_launcher" > <activity android:name=".MainActivity" android:label="@string/app_name" @@ -17,8 +20,9 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <service android:name=".RPCService" + android:process=":RPCServiceProcess" + android:permission="android.permission.BIND_JOB_SERVICE" /> </application> - <uses-permission android:name="android.permission.INTERNET" /> - -</manifest> \ No newline at end of file +</manifest> diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java index 3fc3fc3f1381ea4c1d183fced2a2e68170ac5672..0fca08a2e463ec1da07acd98738bd537dcd0398f 100644 --- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java +++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/MainActivity.java @@ -25,30 +25,18 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Message; + import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.Switch; +import android.content.Intent; + public class MainActivity extends AppCompatActivity { - static final int MSG_RPC_ERROR = 0; - static final String MSG_RPC_ERROR_DATA_KEY = "msg_rpc_error_data_key"; - - private RPCProcessor tvmServerWorker; - @SuppressLint("HandlerLeak") - private final Handler rpcHandler = new Handler() { - @Override - public void dispatchMessage(Message msg) { - Switch switchConnect = findViewById(R.id.switch_connect); - if (msg.what == MSG_RPC_ERROR && switchConnect.isChecked()) { - // switch off and show alert dialog. - switchConnect.setChecked(false); - String msgBody = msg.getData().getString(MSG_RPC_ERROR_DATA_KEY); - showDialog("Error", msgBody); - } - } - }; + + private RPCWatchdog watchdog; private void showDialog(String title, String msg) { AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -71,10 +59,6 @@ public class MainActivity extends AppCompatActivity { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - tvmServerWorker = new RPCProcessor(rpcHandler); - tvmServerWorker.setDaemon(true); - tvmServerWorker.start(); - Switch switchConnect = findViewById(R.id.switch_connect); switchConnect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -88,25 +72,33 @@ public class MainActivity extends AppCompatActivity { } } }); + enableInputView(true); + } @Override protected void onDestroy() { super.onDestroy(); - tvmServerWorker.disconnect(); + if (watchdog != null) { + watchdog.disconnect(); + watchdog = null; + } } private void connectProxy() { EditText edProxyAddress = findViewById(R.id.input_address); EditText edProxyPort = findViewById(R.id.input_port); EditText edAppKey = findViewById(R.id.input_key); - final String proxyHost = edProxyAddress.getText().toString(); final int proxyPort = Integer.parseInt(edProxyPort.getText().toString()); final String key = edAppKey.getText().toString(); - tvmServerWorker.connect(proxyHost, proxyPort, key); + System.err.println("creating watchdog thread..."); + watchdog = new RPCWatchdog(proxyHost, proxyPort, key, this); + + System.err.println("starting watchdog thread..."); + watchdog.start(); SharedPreferences pref = getApplicationContext().getSharedPreferences("RPCProxyPreference", Context.MODE_PRIVATE); SharedPreferences.Editor editor = pref.edit(); @@ -117,8 +109,10 @@ public class MainActivity extends AppCompatActivity { } private void disconnect() { - tvmServerWorker.disconnect(); - System.err.println("Disconnected."); + if (watchdog != null) { + watchdog.disconnect(); + watchdog = null; + } } private void enableInputView(boolean enable) { diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java index 2ff7fee8a6b305ee632d6df509c742896babb3ac..4099084d4ebfec6945e87a55b9146db1917416ca 100644 --- a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java +++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCProcessor.java @@ -34,10 +34,11 @@ class RPCProcessor extends Thread { private String host; private int port; private String key; - private boolean running = false; + private long startTime; private ConnectProxyServerProcessor currProcessor; - private final Handler uiHandler; + private boolean kill = false; + public static final int SESSION_TIMEOUT = 30000; static final SocketFileDescriptorGetter socketFdGetter = new SocketFileDescriptorGetter() { @@ -46,9 +47,18 @@ class RPCProcessor extends Thread { return ParcelFileDescriptor.fromSocket(socket).getFd(); } }; + // callback to initialize the start time of an rpc session + class setTimeCallback implements Runnable { + private RPCProcessor rPCProcessor; + + public setTimeCallback(RPCProcessor rPCProcessor) { + this.rPCProcessor = rPCProcessor; + } - RPCProcessor(Handler uiHandler) { - this.uiHandler = uiHandler; + @Override + public void run() { + rPCProcessor.setStartTime(); + } } @Override public void run() { @@ -61,23 +71,51 @@ class RPCProcessor extends Thread { } catch (InterruptedException e) { } } - currProcessor = new ConnectProxyServerProcessor(host, port, key, socketFdGetter); - } - try { - currProcessor.run(); - } catch (Throwable e) { - disconnect(); - // turn connect switch off. - Message message = new Message(); - message.what = MainActivity.MSG_RPC_ERROR; - Bundle bundle = new Bundle(); - bundle.putString(MainActivity.MSG_RPC_ERROR_DATA_KEY, e.getMessage()); - message.setData(bundle); - uiHandler.sendMessage(message); + // if kill, we do nothing and wait for app restart + // to prevent race where timedOut was reported but restart has not + // happened yet + if (kill) { + System.err.println("waiting for restart..."); + currProcessor = null; + } + else { + startTime = 0; + currProcessor = new ConnectProxyServerProcessor(host, port, key, socketFdGetter); + currProcessor.setStartTimeCallback(new setTimeCallback(this)); + } } + if (currProcessor != null) + currProcessor.run(); } } + /** + * check if the current RPCProcessor has timed out while in a session + */ + synchronized boolean timedOut(long curTime) { + if (startTime == 0) { + return false; + } + else if ((curTime - startTime) > SESSION_TIMEOUT) { + System.err.println("set kill flag..."); + kill = true; + return true; + } + return false; + } + + /** + * set the start time of the current RPC session (used in callback) + */ + synchronized void setStartTime() { + startTime = System.currentTimeMillis(); + System.err.println("start time set to: " + startTime); + } + + synchronized long getStartTime() { + return startTime; + } + /** * Disconnect from the proxy server. */ diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java new file mode 100644 index 0000000000000000000000000000000000000000..facc230f60ae78c2a0bfa7971c181485e1c70498 --- /dev/null +++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCService.java @@ -0,0 +1,64 @@ +package ml.dmlc.tvm.tvmrpc; + +import android.app.Service; +import android.os.IBinder; +import android.content.Intent; + +public class RPCService extends Service { + private String host; + private int port; + private String key; + private int intentNum; + private RPCProcessor tvmServerWorker; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + synchronized(this) { + System.err.println("start command intent"); + // use an alternate kill to prevent android from recycling the + // process + if (intent.getBooleanExtra("kill", false)) { + System.err.println("rpc service received kill..."); + System.exit(0); + } + + this.host = intent.getStringExtra("host"); + this.port = intent.getIntExtra("port", 9090); + this.key = intent.getStringExtra("key"); + System.err.println("got the following: " + this.host + ", " + this.port + ", " + this.key); + System.err.println("intent num: " + this.intentNum); + + if (tvmServerWorker == null) { + System.err.println("service created worker..."); + tvmServerWorker = new RPCProcessor(); + tvmServerWorker.setDaemon(true); + tvmServerWorker.start(); + tvmServerWorker.connect(this.host, this.port, this.key); + } + else if (tvmServerWorker.timedOut(System.currentTimeMillis())) { + System.err.println("rpc service timed out, killing self..."); + System.exit(0); + } + this.intentNum++; + } + // do not restart unless watchdog/app expliciltly does so + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + System.err.println("rpc service got onBind, doing nothing..."); + return null; + } + + @Override + public void onCreate() { + System.err.println("rpc service onCreate..."); + } + + @Override + public void onDestroy() { + tvmServerWorker.disconnect(); + System.err.println("rpc service onDestroy..."); + } +} diff --git a/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java new file mode 100644 index 0000000000000000000000000000000000000000..548a2dfc0e7d4766ecd8cf42ac229189342b5a2b --- /dev/null +++ b/apps/android_rpc/app/src/main/java/ml/dmlc/tvm/tvmrpc/RPCWatchdog.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 ml.dmlc.tvm.tvmrpc; + +import android.content.Context; +import android.content.Intent; + +/** + * Watchdog for RPCService + */ +class RPCWatchdog extends Thread { + public static final int WATCHDOG_POLL_INTERVAL = 5000; + private String host; + private int port; + private String key; + private Context context; + private boolean done = false; + + public RPCWatchdog(String host, int port, String key, Context context) { + super(); + this.host = host; + this.port = port; + this.key = key; + this.context = context; + } + + /** + * Polling loop to check on RPCService status + */ + @Override public void run() { + try { + while (true) { + synchronized (this) { + if (done) { + System.err.println("watchdog done, returning..."); + return; + } + else { + System.err.println("polling rpc service..."); + System.err.println("sending rpc service intent..."); + Intent intent = new Intent(context, RPCService.class); + intent.putExtra("host", host); + intent.putExtra("port", port); + intent.putExtra("key", key); + // will implicilty restart the service if it died + context.startService(intent); + } + } + Thread.sleep(WATCHDOG_POLL_INTERVAL); + } + } catch (InterruptedException e) { + } + } + + /** + * Disconnect from the proxy server. + */ + synchronized void disconnect() { + // kill service + System.err.println("watchdog disconnect call..."); + System.err.println("stopping rpc service..."); + done = true; + Intent intent = new Intent(context, RPCService.class); + intent.putExtra("kill", true); + context.startService(intent); + } +} diff --git a/apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..32a4f0f9157f9959d69ad2fcfa73078a1b5471fe Binary files /dev/null and b/apps/android_rpc/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8e5d4dd8331e76e0f2c7af485ac37b7bb997780e Binary files /dev/null and b/apps/android_rpc/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java index 208efc773b022334e9f7e1512ee0756b3366a97e..2fc97f65aca41a59b4099941d665eaa56d4da2ed 100644 --- a/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java +++ b/jvm/core/src/main/java/ml/dmlc/tvm/rpc/ConnectProxyServerProcessor.java @@ -33,6 +33,7 @@ public class ConnectProxyServerProcessor implements ServerProcessor { private final SocketFileDescriptorGetter socketFileDescriptorGetter; private volatile Socket currSocket = new Socket(); + private Runnable callback; /** * Construct proxy server processor. @@ -48,6 +49,15 @@ public class ConnectProxyServerProcessor implements ServerProcessor { this.key = "server:" + key; socketFileDescriptorGetter = sockFdGetter; } + + /** + * Set a callback when a connection is received e.g., to record the time for a + * watchdog. + * @param callback Runnable object. + */ + public void setStartTimeCallback(Runnable callback) { + this.callback = callback; + } /** * Close the socket. @@ -78,7 +88,9 @@ public class ConnectProxyServerProcessor implements ServerProcessor { int keylen = Utils.wrapBytes(Utils.recvAll(in, 4)).getInt(); String remoteKey = Utils.decodeToStr(Utils.recvAll(in, keylen)); System.err.println("RPCProxy connected to " + address); - + if (callback != null) { + callback.run(); + } final int sockFd = socketFileDescriptorGetter.get(currSocket); if (sockFd != -1) { new NativeServerLoop(sockFd).run();