[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