title: Runnable no longer runs in Service to periodically retrieve from URL
body: <p>I'm working on an Android app that runs on remotely-sited, unattended devices. It has a Service to periodically retrieve settings from a fixed URL. It does this by daisy-chaining posts of a Runnable to a Handler obtained from a HandlerThread.getLooper(): it does an initial post() after creating the Handler in the Service's startup, and thereafter it does a postDelayed() at the end of each run through the Runnable. </p>
  
  <p>This works "forever" on some devices in the project (fetching the settings successfully every 30 seconds for thousands of hours), but on other devices it stops working after a variable amount of time (hours to tens of hours) and thereafter never works again. I've even added code to monitor the Service's activity and restart the process by issuing a new post() if it's been more than 5 minutes since the last run, and that code kicks in when as intended when the process stalls, but still the Runnable no longer runs.</p>
  
  <p>Some pared-down code snippets, and then further discussion of the problem/symptoms:</p>
  
  <pre><code>public class SettingsMonitor extends Service {
  
      private HandlerThread mHandlerThread = new HandlerThread("SettingsMonitorHandler");
      private Handler mSettingsMonitorHandler;
      private long mLastTryMillis = 0;
  
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          mHandlerThread.start();
          mSettingsMonitorHandler = new Handler(mHandlerThread.getLooper());
          mSettingsMonitorHandler.post(checkSettings);
          return START_STICKY;
      }
  
      final Runnable checkSettings = new Runnable()
      {
          @Override
          public void run() {
              Log.d(TAG, "checking latest settings");
              mLastTryMillis = System.currentTimeMillis();
              getSettingsFromUrl(SETTINGS_URL);
              // queue up next check
              mSettingsMonitorHandler.postDelayed(checkSettings, appSettings.getSettingsMonitorIntervalMillis());
          }
      };
  
      public void restartCheckingIfStalled() {
          long settingsMonitorIntervalMillis = appSettings.getSettingsMonitorIntervalMillis();
          long maxStalledMillis = 10 * settingsMonitorIntervalMillis;
          long millisSinceLastCheck = System.currentTimeMillis() - mLastTryMillis;
          if (millisSinceLastCheck &gt; maxStalledMillis) {
              mSettingsMonitorHandler.removeCallbacksAndMessages(null);
              Log.d(TAG, "checkSettings stalled -- restarting...");
              mSettingsMonitorHandler.post(checkSettings);
          }
      }
  
  }
  </code></pre>
  
  <p>The app logs extensive debug info, including every exception caught in local catch blocks (e.g., inside getSettingsFromUrl()) as well as any exception caught in a global app-level UncaughtExceptionHandler -- there are no exceptions being thrown by the app. There are log entries made at many points in getSettingsFromUrl(), as well as the first line of the Runnable, but at some point all of these log entries cease to appear, so I infer that the Runnable itself is no longer actually running.</p>
  
  <p>I monitor the Service from elsewhere in the app and so I know that it is running throughout the lifetime of the app (even after the Runnable no longer runs); at the same time, I call restartCheckingIfStalled() and I <em>do</em> see the log entries indicating that the re-start is being requested, but apparently the post() it issues does not cause the Runnable to run again.</p>
  
  <p>It's a bit difficult to quantify, but it appears that this problem is occurring on devices that are showing other signs of having network connection problems (some are on wi-fi, some are on sim cards). I don't really know what to make of that possible correlation, though.</p>
  
  <p>I am mystified that this process goes off the rails without any sign of trouble -- no exception is thrown, and the last successful run through the Runnable and getSettingsFromUrl() look completely normal. I am looking for suggestions about ways to isolate, identify, and ultimately fix the problem, because I've exhausted a bunch of ideas/tests and I'm still no closer to understanding it.</p>
  
  <p>@x-code: Ok, here's getSettingsFromUrl() -- I don't (explicitly) set a timeout (I'll go search now for whether there is any kind of default). How might lack of timeout relate to or explain the symptoms I'm describing?</p>
  
  <pre><code>    private boolean getSettingsFromUrl(String settingsUrl) {
          Log.d(TAG, "trying settings file: " + settingsUrl);
          try {
              URL url = new URL(settingsUrl);
              Scanner scanner = new Scanner(url.openStream());
              while (scanner.hasNextLine()) {
                  String s = scanner.nextLine().split(SETTINGS_FILE_COMMENT_CHAR)[0].trim();  // remove any comments and trim leading/trailing whitespace
                  if (s.length() &gt; 0) {                                                       // skip blank lines
                      AdminRequest req = new AdminRequest(s);
                      if (req.isSetReq()) {   // only apply SET requests, not GET (or other nonsense)
                          String reqType = req.getReqType();
                          if (settingsToApply.containsKey(reqType)) { // override earlier settings with later
                              settingsToApply.remove(reqType);
                          }
                          settingsToApply.put(reqType, req);
                      }
                  }
              }
              scanner.close();
              return true;
          }
          catch (FileNotFoundException e) {
              Log.d(TAG, "settings file not found at " + settingsUrl);
          }
          catch (Exception e) {
              Log.d(TAG, "error checking settings: " + Log.getStackTraceString(e));
          }
          return false;
      }
  </code></pre>
  
  <p>This may beg further questions (about whether/how I use this function's return value -- which I do; about what these AdminRequests are and what I'm doing with them in the settingsToApply HashMap, etc). I'm willing to continue providing more info if it seems likely to lead to insight, but it's not clear to me why, at a minimum, I don't see the first "checking latest settings" message at the top of checkSettings()... how would a problem (e.g., lack of a timeout in accessing the URL) affect subsequent calls to checkSettings()? Do all invocations of checkSettings() run on the same thread, and it's tied up waiting "forever" to access the URL? If that's the case, why is restartCheckingIfStalled() not also blocked? I <em>do</em> get the Log.d() messages from that.</p>
  
  <p>Edited to show the changes I made to add timeouts, which solved the problem per the responses I received: in getSettingsFromUrl() change these two lines</p>
  
  <pre><code>            URL url = new URL(settingsUrl);
              Scanner scanner = new Scanner(url.openStream());
  </code></pre>
  
  <p>to these:</p>
  
  <pre><code>            URL url = new URL(settingsUrl);
              URLConnection cxn = url.openConnection();
              cxn.setConnectTimeout(URL_CONNECT_TIMEOUT_MILLIS);
              cxn.setReadTimeout(URL_READ_TIMEOUT_MILLIS);
              Scanner scanner = new Scanner(cxn.getInputStream());
  </code></pre>