[tor-commits] [arm/master] Using stem's status listening

atagar at torproject.org atagar at torproject.org
Sat Jan 19 06:58:24 UTC 2013


commit ad287745466cdc42db07d4450378f88d7afa88b3
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Jan 18 22:56:03 2013 -0800

    Using stem's status listening
    
    Dropping our status listening in favor of stem's.
    
    One very minor point of regression is that we now rely on SIGNAL events to get
    sighup notifications rather than listening to NOTICE events. The NOTICE
    listener was a hack so dropping it moving forward is probably for the best.
---
 src/cli/configPanel.py             |    8 +-
 src/cli/connections/connPanel.py   |    9 +--
 src/cli/controller.py              |   10 +-
 src/cli/graphing/bandwidthStats.py |    9 +-
 src/cli/graphing/connStats.py      |   14 ++--
 src/cli/headerPanel.py             |   16 ++---
 src/cli/logPanel.py                |    7 +-
 src/cli/torrcPanel.py              |   13 +--
 src/util/torTools.py               |  142 +----------------------------------
 9 files changed, 47 insertions(+), 181 deletions(-)

diff --git a/src/cli/configPanel.py b/src/cli/configPanel.py
index 32233f2..f37c1d1 100644
--- a/src/cli/configPanel.py
+++ b/src/cli/configPanel.py
@@ -11,6 +11,8 @@ import popups
 
 from util import panel, sysTools, torConfig, torTools, uiTools
 
+import stem.control
+
 from stem.util import conf, enum, str_tools
 
 # TODO: The arm use cases are incomplete since they currently can't be
@@ -202,13 +204,13 @@ class ConfigPanel(panel.Panel):
     # initializes config contents if we're connected
     conn = torTools.getConn()
     conn.addStatusListener(self.resetListener)
-    if conn.isAlive(): self.resetListener(conn, torTools.State.INIT)
+    if conn.isAlive(): self.resetListener(None, stem.control.State.INIT, None)
   
-  def resetListener(self, conn, eventType):
+  def resetListener(self, controller, eventType, _):
     # fetches configuration options if a new instance, otherewise keeps our
     # current contents
     
-    if eventType == torTools.State.INIT:
+    if eventType == stem.control.State.INIT:
       self._loadConfigOptions()
   
   def _loadConfigOptions(self):
diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py
index ddfad9e..b55b937 100644
--- a/src/cli/connections/connPanel.py
+++ b/src/cli/connections/connPanel.py
@@ -12,6 +12,7 @@ import cli.popups
 from cli.connections import countPopup, descriptorPopup, entries, connEntry, circEntry
 from util import connections, panel, torTools, uiTools
 
+from stem.control import State
 from stem.util import conf, enum
 
 # height of the detail panel content, not counting top and bottom border
@@ -117,16 +118,12 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     # listens for when tor stops so we know to stop reflecting changes
     conn.addStatusListener(self.torStateListener)
   
-  def torStateListener(self, conn, eventType):
+  def torStateListener(self, controller, eventType, _):
     """
     Freezes the connection contents when Tor stops.
-    
-    Arguments:
-      conn      - tor controller
-      eventType - type of event detected
     """
     
-    self._isTorRunning = eventType in (torTools.State.INIT, torTools.State.RESET)
+    self._isTorRunning = eventType in (State.INIT, State.RESET)
     
     if self._isTorRunning: self._haltTime = None
     else: self._haltTime = time.time()
diff --git a/src/cli/controller.py b/src/cli/controller.py
index 5b97070..03b05e2 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -20,7 +20,7 @@ import cli.graphing.connStats
 import cli.graphing.resourceStats
 import cli.connections.connPanel
 
-from stem.control import Controller
+from stem.control import State, Controller
 
 from util import connections, hostnames, panel, sysTools, torConfig, torTools
 
@@ -481,7 +481,7 @@ def heartbeatCheck(isUnresponsive):
   
   return isUnresponsive
 
-def connResetListener(conn, eventType):
+def connResetListener(controller, eventType, _):
   """
   Pauses connection resolution when tor's shut down, and resumes with the new
   pid if started again.
@@ -489,9 +489,9 @@ def connResetListener(conn, eventType):
   
   if connections.isResolverAlive("tor"):
     resolver = connections.getResolver("tor")
-    resolver.setPaused(eventType == torTools.State.CLOSED)
+    resolver.setPaused(eventType == State.CLOSED)
     
-    if eventType in (torTools.State.INIT, torTools.State.RESET):
+    if eventType in (State.INIT, State.RESET):
       # Reload the torrc contents. If the torrc panel is present then it will
       # do this instead since it wants to do validation and redraw _after_ the
       # new contents are loaded.
@@ -499,7 +499,7 @@ def connResetListener(conn, eventType):
       if getController().getPanel("torrc") == None:
         torConfig.getTorrc().load(True)
       
-      torPid = conn.getMyPid()
+      torPid = controller.get_info("process/pid", None)
       
       if torPid and torPid != resolver.getPid():
         resolver.setPid(torPid)
diff --git a/src/cli/graphing/bandwidthStats.py b/src/cli/graphing/bandwidthStats.py
index dd0c293..698a2b1 100644
--- a/src/cli/graphing/bandwidthStats.py
+++ b/src/cli/graphing/bandwidthStats.py
@@ -11,6 +11,7 @@ import cli.controller
 from cli.graphing import graphPanel
 from util import torTools, uiTools
 
+from stem.control import State
 from stem.util import conf, log, str_tools, system
 
 def conf_handler(key, value):
@@ -57,7 +58,7 @@ class BandwidthStats(graphPanel.GraphStats):
     # rate/burst and if tor's using accounting
     conn = torTools.getConn()
     self._titleStats, self.isAccounting = [], False
-    if not isPauseBuffer: self.resetListener(conn, torTools.State.INIT) # initializes values
+    if not isPauseBuffer: self.resetListener(conn.getController(), State.INIT, None) # initializes values
     conn.addStatusListener(self.resetListener)
     
     # Initialized the bandwidth totals to the values reported by Tor. This
@@ -89,13 +90,13 @@ class BandwidthStats(graphPanel.GraphStats):
     
     return graphPanel.GraphStats.clone(self, newCopy)
   
-  def resetListener(self, conn, eventType):
+  def resetListener(self, controller, eventType, _):
     # updates title parameters and accounting status if they changed
     self._titleStats = []     # force reset of title
     self.new_desc_event(None) # updates title params
     
-    if eventType in (torTools.State.INIT, torTools.State.RESET) and CONFIG["features.graph.bw.accounting.show"]:
-      isAccountingEnabled = conn.getInfo('accounting/enabled', None) == '1'
+    if eventType in (State.INIT, State.RESET) and CONFIG["features.graph.bw.accounting.show"]:
+      isAccountingEnabled = controller.get_info('accounting/enabled', None) == '1'
       
       if isAccountingEnabled != self.isAccounting:
         self.isAccounting = isAccountingEnabled
diff --git a/src/cli/graphing/connStats.py b/src/cli/graphing/connStats.py
index 0df5db3..88ed44a 100644
--- a/src/cli/graphing/connStats.py
+++ b/src/cli/graphing/connStats.py
@@ -5,6 +5,8 @@ Tracks stats concerning tor's current connections.
 from cli.graphing import graphPanel
 from util import connections, torTools
 
+from stem.control import State
+
 class ConnStats(graphPanel.GraphStats):
   """
   Tracks number of connections, counting client and directory connections as 
@@ -17,18 +19,18 @@ class ConnStats(graphPanel.GraphStats):
     # listens for tor reload (sighup) events which can reset the ports tor uses
     conn = torTools.getConn()
     self.orPort, self.dirPort, self.controlPort = "0", "0", "0"
-    self.resetListener(conn, torTools.State.INIT) # initialize port values
+    self.resetListener(conn.getController(), State.INIT, None) # initialize port values
     conn.addStatusListener(self.resetListener)
   
   def clone(self, newCopy=None):
     if not newCopy: newCopy = ConnStats()
     return graphPanel.GraphStats.clone(self, newCopy)
   
-  def resetListener(self, conn, eventType):
-    if eventType in (torTools.State.INIT, torTools.State.RESET):
-      self.orPort = conn.getOption("ORPort", "0")
-      self.dirPort = conn.getOption("DirPort", "0")
-      self.controlPort = conn.getOption("ControlPort", "0")
+  def resetListener(self, controller, eventType, _):
+    if eventType in (State.INIT, State.RESET):
+      self.orPort = controller.get_conf("ORPort", "0")
+      self.dirPort = controller.get_conf("DirPort", "0")
+      self.controlPort = controller.get_conf("ControlPort", "0")
   
   def eventTick(self):
     """
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index 29d5f74..b1bd65e 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -22,7 +22,7 @@ import threading
 import stem
 import stem.connection
 
-from stem.control import Controller
+from stem.control import State, Controller
 from stem.util import conf, str_tools
 
 import starter
@@ -232,7 +232,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
         self.addstr(1, x, "Relaying Disabled", uiTools.getColor("cyan"))
         x += 17
       else:
-        statusTime = torTools.getConn().getStatus()[1]
+        statusTime = torTools.getConn().getHeartbeat()
         
         if statusTime:
           statusTimeLabel = time.strftime("%H:%M %m/%d/%Y, ", time.localtime(statusTime))
@@ -334,7 +334,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
           self.addstr(y, x, "none", curses.A_BOLD | uiTools.getColor("cyan"))
       else:
         y = 2 if isWide else 4
-        statusTime = torTools.getConn().getStatus()[1]
+        statusTime = torTools.getConn().getHeartbeat()
         statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime))
         self.addstr(y, 0, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red"))
         self.addstr(y, 16, " (%s) - press r to reconnect" % statusTimeLabel)
@@ -434,16 +434,12 @@ class HeaderPanel(panel.Panel, threading.Thread):
     self._cond.notifyAll()
     self._cond.release()
   
-  def resetListener(self, _, eventType):
+  def resetListener(self, controller, eventType, _):
     """
     Updates static parameters on tor reload (sighup) events.
-    
-    Arguments:
-      conn      - tor controller
-      eventType - type of event detected
     """
     
-    if eventType in (torTools.State.INIT, torTools.State.RESET):
+    if eventType in (State.INIT, State.RESET):
       initialHeight = self.getHeight()
       self._isTorConnected = True
       self._haltTime = None
@@ -457,7 +453,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       else:
         # just need to redraw ourselves
         self.redraw(True)
-    elif eventType == torTools.State.CLOSED:
+    elif eventType == State.CLOSED:
       self._isTorConnected = False
       self._haltTime = time.time()
       self._update()
diff --git a/src/cli/logPanel.py b/src/cli/logPanel.py
index ecb3109..b8a0c40 100644
--- a/src/cli/logPanel.py
+++ b/src/cli/logPanel.py
@@ -12,6 +12,7 @@ import logging
 import threading
 
 import stem
+from stem.control import State
 from stem.response import events
 from stem.util import conf, log, system
 
@@ -1132,13 +1133,15 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     # provides back the input set minus events we failed to set
     return sorted(torEvents.union(armEvents))
   
-  def _resetListener(self, _, eventType):
+  def _resetListener(self, controller, eventType, _):
     # if we're attaching to a new tor instance then clears the log and
     # prepopulates it with the content belonging to this instance
     
-    if eventType == torTools.State.INIT:
+    if eventType == State.INIT:
       self.reprepopulateEvents()
       self.redraw(True)
+    elif eventType == State.CLOSED:
+      log.notice("Tor control port closed")
   
   def _getTitle(self, width):
     """
diff --git a/src/cli/torrcPanel.py b/src/cli/torrcPanel.py
index 635fc85..c9d83e6 100644
--- a/src/cli/torrcPanel.py
+++ b/src/cli/torrcPanel.py
@@ -10,6 +10,7 @@ import popups
 
 from util import panel, torConfig, torTools, uiTools
 
+from stem.control import State
 from stem.util import conf, enum
 
 def conf_handler(key, value):
@@ -48,18 +49,14 @@ class TorrcPanel(panel.Panel):
     # listens for tor reload (sighup) events
     conn = torTools.getConn()
     conn.addStatusListener(self.resetListener)
-    if conn.isAlive(): self.resetListener(conn, torTools.State.INIT)
+    if conn.isAlive(): self.resetListener(None, State.INIT, None)
   
-  def resetListener(self, conn, eventType):
+  def resetListener(self, controller, eventType, _):
     """
     Reloads and displays the torrc on tor reload (sighup) events.
-    
-    Arguments:
-      conn      - tor controller
-      eventType - type of event detected
     """
     
-    if eventType == torTools.State.INIT:
+    if eventType == State.INIT:
       # loads the torrc and provides warnings in case of validation errors
       try:
         loadedTorrc = torConfig.getTorrc()
@@ -67,7 +64,7 @@ class TorrcPanel(panel.Panel):
         loadedTorrc.logValidationIssues()
         self.redraw(True)
       except: pass
-    elif eventType == torTools.State.RESET:
+    elif eventType == State.RESET:
       try:
         torConfig.getTorrc().load(True)
         self.redraw(True)
diff --git a/src/util/torTools.py b/src/util/torTools.py
index 2b375b8..f38a8ab 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -19,12 +19,6 @@ from util import connections
 
 from stem.util import conf, enum, log, proc, str_tools, system
 
-# enums for tor's controller state:
-# INIT - attached to a new controller
-# RESET - received a reset/sighup signal
-# CLOSED - control port closed
-State = enum.Enum("INIT", "RESET", "CLOSED")
-
 # Addresses of the default directory authorities for tor version 0.2.3.0-alpha
 # (this comes from the dirservers array in src/or/config.c).
 DIR_SERVERS = [("86.59.21.38", "80"),         # tor26
@@ -51,23 +45,11 @@ CONFIG = conf.config_dict("arm", {
 })
 
 # events used for controller functionality:
-# NOTICE - used to detect when tor is shut down
 # NEWDESC, NS, and NEWCONSENSUS - used for cache invalidation
-REQ_EVENTS = {"NOTICE": "this will be unable to detect when tor is shut down",
-              "NEWDESC": "information related to descriptors will grow stale",
+REQ_EVENTS = {"NEWDESC": "information related to descriptors will grow stale",
               "NS": "information related to the consensus will grow stale",
               "NEWCONSENSUS": "information related to the consensus will grow stale"}
 
-# This prevents controllers from spawning worker threads (and by extension
-# notifying status listeners). This is important when shutting down to prevent
-# rogue threads from being alive during shutdown.
-
-NO_SPAWN = False
-
-# Flag to indicate if we're handling our first init signal. This is for
-# startup performance so we don't introduce a sleep while initializing.
-IS_STARTUP_SIGNAL = True
-
 def getPid(controlPort=9051, pidFilePath=None):
   """
   Attempts to determine the process id for a running tor process, using the
@@ -259,7 +241,6 @@ class Controller:
   def __init__(self):
     self.controller = None
     self.connLock = threading.RLock()
-    self.statusListeners = []           # callback functions for tor's state changes
     self.controllerEvents = []          # list of successfully set controller events
     self._fingerprintMappings = None    # mappings of ip -> [(port, fingerprint), ...]
     self._fingerprintLookupCache = {}   # lookup cache with (ip, port) -> fingerprint mappings
@@ -270,19 +251,8 @@ class Controller:
     self._consensusLookupCache = {}     # lookup cache with network status entries
     self._descriptorLookupCache = {}    # lookup cache with relay descriptors
     self._isReset = False               # internal flag for tracking resets
-    self._status = State.CLOSED         # current status of the attached control port
-    self._statusTime = 0                # unix time-stamp for the duration of the status
     self._lastNewnym = 0                # time we last sent a NEWNYM signal
     
-    # Status signaling for when tor starts, stops, or is reset is done via
-    # enquing the signal then spawning a handler thread. This is to provide
-    # safety in race conditions, for instance if we sighup with a torrc that
-    # causes tor to crash then we'll get both an INIT and CLOSED signal. It's
-    # important in those cases that listeners get the correct signal last (in
-    # that case CLOSED) so they aren't confused about what tor's current state
-    # is.
-    self._notificationQueue = Queue.Queue()
-    
     # Logs issues and notices when fetching the path prefix if true. This is
     # only done once for the duration of the application to avoid pointless
     # messages.
@@ -310,7 +280,6 @@ class Controller:
       self.controller = controller
       log.info("Stem connected to tor version %s" % self.controller.get_version())
       
-      self.controller.add_event_listener(self.msg_event, stem.control.EventType.NOTICE)
       self.controller.add_event_listener(self.ns_event, stem.control.EventType.NS)
       self.controller.add_event_listener(self.new_consensus_event, stem.control.EventType.NEWCONSENSUS)
       self.controller.add_event_listener(self.new_desc_event, stem.control.EventType.NEWDESC)
@@ -326,17 +295,9 @@ class Controller:
       self._consensusLookupCache = {}
       self._descriptorLookupCache = {}
       
-      self._status = State.INIT
-      self._statusTime = time.time()
-      
       # time that we sent our last newnym signal
       self._lastNewnym = 0
       
-      # notifies listeners that a new controller is available
-      if not NO_SPAWN:
-        self._notificationQueue.put(State.INIT)
-        thread.start_new_thread(self._notifyStatusListeners, ())
-      
       self.connLock.release()
   
   def close(self):
@@ -348,18 +309,12 @@ class Controller:
     if self.controller:
       self.controller.close()
       self.controller = None
-      
-      self._status = State.CLOSED
-      self._statusTime = time.time()
-      
-      # notifies listeners that the controller's been shut down
-      if not NO_SPAWN:
-        self._notificationQueue.put(State.CLOSED)
-        thread.start_new_thread(self._notifyStatusListeners, ())
-      
       self.connLock.release()
     else: self.connLock.release()
   
+  def getController(self):
+    return self.controller
+
   def isAlive(self):
     """
     Returns True if this has been initialized with a working stem instance,
@@ -754,15 +709,6 @@ class Controller:
     
     return self._getRelayAttr("startTime", None)
   
-  def getStatus(self):
-    """
-    Provides a tuple consisting of the control port's current status and unix
-    time-stamp for when it became this way (zero if no status has yet to be
-    set).
-    """
-    
-    return (self._status, self._statusTime)
-  
   def isExitingAllowed(self, ipAddress, port):
     """
     Checks if the given destination can be exited to by this relay, returning
@@ -1090,21 +1036,7 @@ class Controller:
                  myFunction(controller, eventType)
     """
     
-    self.statusListeners.append(callback)
-  
-  def removeStatusListener(self, callback):
-    """
-    Stops listener from being notified of further events. This returns true if a
-    listener's removed, false otherwise.
-    
-    Arguments:
-      callback - functor to be removed
-    """
-    
-    if callback in self.statusListeners:
-      self.statusListeners.remove(callback)
-      return True
-    else: return False
+    self.controller.add_status_listener(callback)
   
   def getControllerEvents(self):
     """
@@ -1218,27 +1150,6 @@ class Controller:
     
     if raisedException: raise raisedException
   
-  def msg_event(self, event):
-    """
-    Listens for reload signal (hup), which is either produced by:
-    causing the torrc and internal state to be reset.
-    """
-    
-    if event.runlevel == "NOTICE" and event.message.startswith("Received reload signal (hup)"):
-      self.connLock.acquire()
-      
-      if self.isAlive():
-        self._isReset = True
-        
-        self._status = State.RESET
-        self._statusTime = time.time()
-        
-        if not NO_SPAWN:
-          self._notificationQueue.put(State.RESET)
-          thread.start_new_thread(self._notifyStatusListeners, ())
-      
-      self.connLock.release()
-  
   def ns_event(self, event):
     self._consensusLookupCache = {}
     
@@ -1720,47 +1631,4 @@ class Controller:
     
     if result == None or result == UNKNOWN: return default
     else: return result
-  
-  def _notifyStatusListeners(self):
-    """
-    Sends a notice to all current listeners that a given change in tor's
-    controller status has occurred.
-    
-    Arguments:
-      eventType - enum representing tor's new status
-    """
-    
-    global IS_STARTUP_SIGNAL
-    
-    # If there's a quick race state (for instance a sighup causing both an init
-    # and close event) then give them a moment to enqueue. This way we can
-    # coles the events and discard the inaccurate one.
-    
-    if not IS_STARTUP_SIGNAL:
-      time.sleep(0.2)
-    else: IS_STARTUP_SIGNAL = False
-    
-    self.connLock.acquire()
-    
-    try:
-      eventType = self._notificationQueue.get(timeout=0)
-      
-      # checks that the notice is accurate for our current state
-      if self.isAlive() != (eventType in (State.INIT, State.RESET)):
-        eventType = None
-    except Queue.Empty:
-      eventType = None
-    
-    if eventType:
-      # resets cached GETINFO and GETCONF parameters
-      self._cachedParam = {}
-      
-      # gives a notice that the control port has closed
-      if eventType == State.CLOSED:
-        log.notice("Tor control port closed")
-      
-      for callback in self.statusListeners:
-        callback(self, eventType)
-    
-    self.connLock.release()
 



More information about the tor-commits mailing list