[tor-commits] [nyx/master] Drop log panel lock

atagar at torproject.org atagar at torproject.org
Fri May 15 16:59:04 UTC 2015


commit fcf97845dbc0f49c717979bb786e679ef4c980b8
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri May 15 09:08:15 2015 -0700

    Drop log panel lock
    
    Drop the lock we used to prevent concurrent modifications of our content while
    we redraw. This was half-assed (not covering all our setters). Safer to just
    make our draw function make shallow copies of what it needs.
---
 nyx/log_panel.py |  195 +++++++++++++++++++++++++++---------------------------
 nyx/util/log.py  |    7 ++
 2 files changed, 105 insertions(+), 97 deletions(-)

diff --git a/nyx/log_panel.py b/nyx/log_panel.py
index a76340d..4180f57 100644
--- a/nyx/log_panel.py
+++ b/nyx/log_panel.py
@@ -69,15 +69,15 @@ class LogPanel(panel.Panel, threading.Thread):
     threading.Thread.__init__(self)
     self.setDaemon(True)
 
-    self._logged_events = nyx.util.log.LogGroup(CONFIG['cache.log_panel.size'], group_by_day = True)
-    self._logged_event_types = nyx.util.log.listen_for_events(self._register_tor_event, logged_events)
+    self._event_log = nyx.util.log.LogGroup(CONFIG['cache.log_panel.size'], group_by_day = True)
+    self._event_types = nyx.util.log.listen_for_events(self._register_tor_event, logged_events)
     self._log_file = nyx.util.log.LogFileOutput(CONFIG['features.logFile'])
     self._filter = nyx.util.log.LogFilters(initial_filters = CONFIG['features.log.regex'])
+    self._show_duplicates = CONFIG['features.log.showDuplicateEntries']
 
-    self.set_pause_attr('_logged_events')
+    self.set_pause_attr('_event_log')
 
     self._halt = False  # terminates thread if true
-    self._lock = threading.RLock()
     self._pause_condition = threading.Condition()
     self._has_new_event = False
 
@@ -89,14 +89,14 @@ class LogPanel(panel.Panel, threading.Thread):
       if log_location:
         try:
           for entry in reversed(list(nyx.util.log.read_tor_log(log_location, CONFIG['features.log.prepopulateReadLimit']))):
-            if entry.type in self._logged_event_types:
-              self._logged_events.add(entry)
+            if entry.type in self._event_types:
+              self._event_log.add(entry)
         except IOError as exc:
           log.info('Unable to read log located at %s: %s' % (log_location, exc))
         except ValueError as exc:
           log.info(str(exc))
 
-    self._last_content_height = len(self._logged_events)  # height of the rendered content when last drawn
+    self._last_content_height = len(self._event_log)  # height of the rendered content when last drawn
     self._scroll = 0
 
     # merge NYX_LOGGER into us, and listen for its future events
@@ -114,8 +114,7 @@ class LogPanel(panel.Panel, threading.Thread):
       they're deduplicated
     """
 
-    nyx_config = conf.get_config('nyx')
-    nyx_config.set('features.log.showDuplicateEntries', str(is_visible))
+    self._show_duplicates = is_visible
 
   def get_filter(self):
     """
@@ -163,10 +162,9 @@ class LogPanel(panel.Panel, threading.Thread):
             user_input = user_input.replace(' ', '')  # strip spaces
             event_types = nyx.arguments.expand_events(user_input)
 
-            if event_types != self._logged_event_types:
-              with self._lock:
-                self._logged_event_types = nyx.util.log.listen_for_events(self._register_tor_event, event_types)
-                self.redraw(True)
+            if event_types != self._event_types:
+              self._event_types = nyx.util.log.listen_for_events(self._register_tor_event, event_types)
+              self.redraw(True)
           except ValueError as exc:
             nyx.popups.show_msg('Invalid flags: %s' % str(exc), 2)
       finally:
@@ -191,9 +189,8 @@ class LogPanel(panel.Panel, threading.Thread):
     Clears the contents of the event log.
     """
 
-    with self._lock:
-      self._logged_events = nyx.util.log.LogGroup(CONFIG['cache.log_panel.size'], group_by_day = True)
-      self.redraw(True)
+    self._event_log = nyx.util.log.LogGroup(CONFIG['cache.log_panel.size'], group_by_day = True)
+    self.redraw(True)
 
   def save_snapshot(self, path):
     """
@@ -217,14 +214,16 @@ class LogPanel(panel.Panel, threading.Thread):
     except OSError as exc:
       raise IOError("unable to make directory '%s'" % base_dir)
 
-    with self._lock:
-      with open(path, 'w') as snapshot_file:
-        try:
-          for entry in reversed(list(self._logged_events)):
-            if self._filter.match(entry.display_message):
-              snapshot_file.write(entry.display_message + '\n')
-        except Exception as exc:
-          raise IOError("unable to write to '%s': %s" % (path, exc))
+    event_log = list(self._event_log)
+    event_filter = self._filter.clone()
+
+    with open(path, 'w') as snapshot_file:
+      try:
+        for entry in reversed(event_log):
+          if event_filter.match(entry.display_message):
+            snapshot_file.write(entry.display_message + '\n')
+      except Exception as exc:
+        raise IOError("unable to write to '%s': %s" % (path, exc))
 
   def handle_key(self, key):
     if key.is_scroll():
@@ -232,13 +231,11 @@ class LogPanel(panel.Panel, threading.Thread):
       new_scroll = ui_tools.get_scroll_position(key, self._scroll, page_height, self._last_content_height)
 
       if self._scroll != new_scroll:
-        with self._lock:
-          self._scroll = new_scroll
-          self.redraw(True)
-    elif key.match('u'):
-      with self._lock:
-        self.set_duplicate_visability(not CONFIG['features.log.showDuplicateEntries'])
+        self._scroll = new_scroll
         self.redraw(True)
+    elif key.match('u'):
+      self.set_duplicate_visability(not self._show_duplicates)
+      self.redraw(True)
     elif key.match('c'):
       msg = 'This will clear the log. Are you sure (c again to confirm)?'
       key_press = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
@@ -274,98 +271,103 @@ class LogPanel(panel.Panel, threading.Thread):
       ('a', 'save snapshot of the log', None),
       ('e', 'change logged events', None),
       ('f', 'log regex filter', 'enabled' if self._filter.selection() else 'disabled'),
-      ('u', 'duplicate log entries', 'visible' if CONFIG['features.log.showDuplicateEntries'] else 'hidden'),
+      ('u', 'duplicate log entries', 'visible' if self._show_duplicates else 'hidden'),
       ('c', 'clear event log', None),
     ]
 
   def draw(self, width, height):
-    with self._lock:
-      event_log = list(self.get_attr('_logged_events'))
-      self._scroll = max(0, min(self._scroll, self._last_content_height - height + 1))
+    self._scroll = max(0, min(self._scroll, self._last_content_height - height + 1))
 
-      is_scroll_bar_visible = self._last_content_height > height - 1
+    event_log = list(self.get_attr('_event_log'))
+    event_filter = self._filter.clone()
+    event_types = list(self._event_types)
+    scroll = int(self._scroll)
+    last_content_height = int(self._last_content_height)
+    show_duplicates = self._show_duplicates
 
-      if is_scroll_bar_visible:
-        self.add_scroll_bar(self._scroll, self._scroll + height - 1, self._last_content_height, 1)
+    is_scroll_bar_visible = last_content_height > height - 1
 
-      x, y = 3 if is_scroll_bar_visible else 1, 1 - self._scroll
+    if is_scroll_bar_visible:
+      self.add_scroll_bar(scroll, scroll + height - 1, last_content_height, 1)
 
-      # group entries by date, filtering out those that aren't visible
+    x, y = 3 if is_scroll_bar_visible else 1, 1 - scroll
 
-      day_to_entries, today = {}, nyx.util.log.day_count(time.time())
+    # group entries by date, filtering out those that aren't visible
 
-      for entry in event_log:
-        if entry.is_duplicate and not CONFIG['features.log.showDuplicateEntries']:
-          continue  # deduplicated message
-        elif not self._filter.match(entry.display_message):
-          continue  # filter doesn't match log message
+    day_to_entries, today = {}, nyx.util.log.day_count(time.time())
 
-        day_to_entries.setdefault(entry.day_count(), []).append(entry)
+    for entry in event_log:
+      if entry.is_duplicate and not show_duplicates:
+        continue  # deduplicated message
+      elif not event_filter.match(entry.display_message):
+        continue  # filter doesn't match log message
 
-      for day in sorted(day_to_entries.keys(), reverse = True):
-        if day == today:
-          for entry in day_to_entries[day]:
-            y = self._draw_entry(x, y, width, entry)
-        else:
-          original_y, y = y, y + 1
+      day_to_entries.setdefault(entry.day_count(), []).append(entry)
 
-          for entry in day_to_entries[day]:
-            y = self._draw_entry(x, y, width, entry)
+    for day in sorted(day_to_entries.keys(), reverse = True):
+      if day == today:
+        for entry in day_to_entries[day]:
+          y = self._draw_entry(x, y, width, entry, show_duplicates)
+      else:
+        original_y, y = y, y + 1
 
-          ui_tools.draw_box(self, original_y, x - 1, width - x + 1, y - original_y + 1, curses.A_BOLD, 'yellow')
-          time_label = time.strftime(' %B %d, %Y ', time.localtime(day_to_entries[day][0].timestamp))
-          self.addstr(original_y, x + 1, time_label, curses.A_BOLD, curses.A_BOLD, 'yellow')
+        for entry in day_to_entries[day]:
+          y = self._draw_entry(x, y, width, entry, show_duplicates)
 
-          y += 1
+        ui_tools.draw_box(self, original_y, x - 1, width - x + 1, y - original_y + 1, curses.A_BOLD, 'yellow')
+        time_label = time.strftime(' %B %d, %Y ', time.localtime(day_to_entries[day][0].timestamp))
+        self.addstr(original_y, x + 1, time_label, curses.A_BOLD, curses.A_BOLD, 'yellow')
 
-      # drawing the title after the content, so we'll clear content from the top line
+        y += 1
 
-      if self.is_title_visible():
-        self._draw_title(width)
+    # drawing the title after the content, so we'll clear content from the top line
 
-      # redraw the display if...
-      # - last_content_height was off by too much
-      # - we're off the bottom of the page
+    if self.is_title_visible():
+      self._draw_title(width, event_types, event_filter)
 
-      new_content_height = y + self._scroll - 1
-      content_height_delta = abs(self._last_content_height - new_content_height)
-      force_redraw, force_redraw_reason = True, ''
+    # redraw the display if...
+    # - last_content_height was off by too much
+    # - we're off the bottom of the page
 
-      if content_height_delta >= CONTENT_HEIGHT_REDRAW_THRESHOLD:
-        force_redraw_reason = 'estimate was off by %i' % content_height_delta
-      elif new_content_height > height and self._scroll + height - 1 > new_content_height:
-        force_redraw_reason = 'scrolled off the bottom of the page'
-      elif not is_scroll_bar_visible and new_content_height > height - 1:
-        force_redraw_reason = "scroll bar wasn't previously visible"
-      elif is_scroll_bar_visible and new_content_height <= height - 1:
-        force_redraw_reason = "scroll bar shouldn't be visible"
-      else:
-        force_redraw = False
+    new_content_height = y + scroll - 1
+    content_height_delta = abs(last_content_height - new_content_height)
+    force_redraw, force_redraw_reason = True, ''
 
-      self._last_content_height = new_content_height
-      self._has_new_event = False
+    if content_height_delta >= CONTENT_HEIGHT_REDRAW_THRESHOLD:
+      force_redraw_reason = 'estimate was off by %i' % content_height_delta
+    elif new_content_height > height and self._scroll + height - 1 > new_content_height:
+      force_redraw_reason = 'scrolled off the bottom of the page'
+    elif not is_scroll_bar_visible and new_content_height > height - 1:
+      force_redraw_reason = "scroll bar wasn't previously visible"
+    elif is_scroll_bar_visible and new_content_height <= height - 1:
+      force_redraw_reason = "scroll bar shouldn't be visible"
+    else:
+      force_redraw = False
 
-      if force_redraw:
-        log.debug('redrawing the log panel with the corrected content height (%s)' % force_redraw_reason)
-        self.redraw(True)
+    self._last_content_height = new_content_height
+    self._has_new_event = False
 
-  def _draw_title(self, width):
+    if force_redraw:
+      log.debug('redrawing the log panel with the corrected content height (%s)' % force_redraw_reason)
+      self.redraw(True)
+
+  def _draw_title(self, width, event_types, event_filter):
     """
     Panel title with the event types we're logging and our regex filter if set.
     """
 
     self.addstr(0, 0, ' ' * width)  # clear line
-    title_comp = list(nyx.util.log.condense_runlevels(*self._logged_event_types))
+    title_comp = list(nyx.util.log.condense_runlevels(*event_types))
 
-    if self._filter.selection():
-      title_comp.append('filter: %s' % self._filter.selection())
+    if event_filter.selection():
+      title_comp.append('filter: %s' % event_filter.selection())
 
     title_comp_str = join(title_comp, ', ', width - 10)
     title = 'Events (%s):' % title_comp_str if title_comp_str else 'Events:'
 
     self.addstr(0, 0, title, curses.A_STANDOUT)
 
-  def _draw_entry(self, x, y, width, entry):
+  def _draw_entry(self, x, y, width, entry, show_duplicates):
     """
     Presents a log entry with line wrapping.
     """
@@ -397,7 +399,7 @@ class LogPanel(panel.Panel, threading.Thread):
 
     x, y = draw_msg(min_x, x, y, width, msg, boldness, color)
 
-    if entry.duplicates and not CONFIG['features.log.showDuplicateEntries']:
+    if entry.duplicates and not show_duplicates:
       duplicate_count = len(entry.duplicates) - 1
       plural = 's' if duplicate_count > 1 else ''
       duplicate_msg = ' [%i duplicate%s hidden]' % (duplicate_count, plural)
@@ -462,17 +464,16 @@ class LogPanel(panel.Panel, threading.Thread):
     self._register_event(nyx.util.log.LogEntry(int(record.created), 'NYX_%s' % record.levelname, record.msg))
 
   def _register_event(self, event):
-    if event.type not in self._logged_event_types:
+    if event.type not in self._event_types:
       return
 
-    with self._lock:
-      self._logged_events.add(event)
-      self._log_file.write(event.display_message)
+    self._event_log.add(event)
+    self._log_file.write(event.display_message)
 
-      # notifies the display that it has new content
+    # notifies the display that it has new content
 
-      if self._filter.match(event.display_message):
-        self._has_new_event = True
+    if self._filter.match(event.display_message):
+      self._has_new_event = True
 
-        with self._pause_condition:
-          self._pause_condition.notifyAll()
+      with self._pause_condition:
+        self._pause_condition.notifyAll()
diff --git a/nyx/util/log.py b/nyx/util/log.py
index ca2b520..c2e1a4c 100644
--- a/nyx/util/log.py
+++ b/nyx/util/log.py
@@ -392,6 +392,13 @@ class LogFilters(object):
     regex_filter = self._past_filters.get(self._selected)
     return not regex_filter or bool(regex_filter.search(message))
 
+  def clone(self):
+    with self._lock:
+      clone = LogFilters(max_filters = self._max_filters)
+      clone._selected = self._selected
+      clone._past_filters = self._past_filters
+      return clone
+
 
 def trace(msg, **attr):
   _log(stem.util.log.TRACE, msg, **attr)



More information about the tor-commits mailing list