<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[13484] CalendarServer/trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/13484">13484</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-05-15 18:54:55 -0700 (Thu, 15 May 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Fix base class ordering to ensure schedule work group locking occurs. Fix dashboard so it can run even
when the server is down or being started/stopped.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarserverdashboard_servicepy">CalendarServer/trunk/calendarserver/dashboard_service.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolsdashboardpy">CalendarServer/trunk/calendarserver/tools/dashboard.py</a></li>
<li><a href="#CalendarServertrunkrequirementsstabletxt">CalendarServer/trunk/requirements-stable.txt</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingworkpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcalendarserverdashboard_servicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/dashboard_service.py (13483 => 13484)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/dashboard_service.py        2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/calendarserver/dashboard_service.py        2014-05-16 01:54:55 UTC (rev 13484)
</span><span class="lines">@@ -124,9 +124,12 @@
</span><span class="cx"> @rtype: L{str}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- txn = self.factory.store.newTransaction()
- records = (yield JobItem.histogram(txn))
- yield txn.commit()
</del><ins>+ if self.factory.store:
+ txn = self.factory.store.newTransaction()
+ records = (yield JobItem.histogram(txn))
+ yield txn.commit()
+ else:
+ records = {}
</ins><span class="cx">
</span><span class="cx"> returnValue(records)
</span><span class="cx">
</span><span class="lines">@@ -139,9 +142,13 @@
</span><span class="cx"> @rtype: L{str}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- queuer = self.factory.store.queuer
- loads = queuer.workerPool.eachWorkerLoad()
- level = queuer.workerPool.loadLevel()
</del><ins>+ if self.factory.store:
+ queuer = self.factory.store.queuer
+ loads = queuer.workerPool.eachWorkerLoad()
+ level = queuer.workerPool.loadLevel()
+ else:
+ loads = []
+ level = 0
</ins><span class="cx">
</span><span class="cx"> return succeed({"workers": loads, "level": level})
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolsdashboardpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/dashboard.py (13483 => 13484)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/dashboard.py        2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/calendarserver/tools/dashboard.py        2014-05-16 01:54:55 UTC (rev 13484)
</span><span class="lines">@@ -96,6 +96,11 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><ins>+def defaultIfNone(x, default):
+ return x if x is not None else default
+
+
+
</ins><span class="cx"> class Dashboard(object):
</span><span class="cx"> """
</span><span class="cx"> Main dashboard controller. Use Python's L{sched} feature to schedule
</span><span class="lines">@@ -113,6 +118,7 @@
</span><span class="cx"> self.seconds = 0.1 if usesCurses else 1.0
</span><span class="cx"> self.sched = sched.scheduler(time.time, time.sleep)
</span><span class="cx"> self.client = DashboardClient(("localhost", 8100), True)
</span><ins>+ self.client_error = False
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="lines">@@ -160,11 +166,34 @@
</span><span class="cx"> self.updateDisplay(True)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def resetWindows(self):
+ """
+ Reset the current set of windows.
+ """
+ if self.windows:
+ for window in self.windows:
+ window.deactivate()
+ old_windows = self.windows
+ self.windows = []
+ top = 0
+ for old in old_windows:
+ self.windows.append(old.__class__(self.usesCurses, self.client).makeWindow(top=top))
+ self.windows[-1].activate()
+ top += self.windows[-1].nlines + 1
+
+
</ins><span class="cx"> def updateDisplay(self, initialUpdate=False):
</span><span class="cx"> """
</span><span class="cx"> Periodic update of the current window and check for a key press.
</span><span class="cx"> """
</span><span class="cx"> self.client.update()
</span><ins>+ client_error = len(self.client.currentData) == 0
+ if client_error ^ self.client_error:
+ self.client_error = client_error
+ self.resetWindows()
+ elif filter(lambda x: x.requiresReset(), self.windows):
+ self.resetWindows()
+
</ins><span class="cx"> try:
</span><span class="cx"> if not self.paused or initialUpdate:
</span><span class="cx"> for window in filter(
</span><span class="lines">@@ -172,8 +201,9 @@
</span><span class="cx"> self.windows
</span><span class="cx"> ):
</span><span class="cx"> window.update()
</span><del>- except Exception as e:
- print(str(e))
</del><ins>+ except Exception as e: #@UnusedVariable
+ #print(str(e))
+ pass
</ins><span class="cx"> if not self.usesCurses:
</span><span class="cx"> print("-------------")
</span><span class="cx">
</span><span class="lines">@@ -208,7 +238,7 @@
</span><span class="cx"> self.socket = None
</span><span class="cx"> self.sockname = sockname
</span><span class="cx"> self.useTCP = useTCP
</span><del>- self.currentData = None
</del><ins>+ self.currentData = {}
</ins><span class="cx"> self.items = []
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -223,21 +253,26 @@
</span><span class="cx"> self.socket.setblocking(0)
</span><span class="cx"> self.socket.sendall(json.dumps(items) + "\r\n")
</span><span class="cx"> data = ""
</span><ins>+ t = time.time()
</ins><span class="cx"> while not data.endswith("\n"):
</span><span class="cx"> try:
</span><span class="cx"> d = self.socket.recv(1024)
</span><span class="cx"> except socket.error as se:
</span><span class="cx"> if se.args[0] != errno.EWOULDBLOCK:
</span><span class="cx"> raise
</span><ins>+ if time.time() - t > 5:
+ raise socket.error
</ins><span class="cx"> continue
</span><span class="cx"> if d:
</span><span class="cx"> data += d
</span><span class="cx"> else:
</span><span class="cx"> break
</span><span class="cx"> data = json.loads(data)
</span><del>- except socket.error as e:
- data = {"Failed": "Unable to read statistics from server: %s %s" % (self.sockname, e)}
</del><ins>+ except socket.error:
+ data = {}
</ins><span class="cx"> self.socket = None
</span><ins>+ except ValueError:
+ data = {}
</ins><span class="cx"> return data
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -245,6 +280,7 @@
</span><span class="cx"> """
</span><span class="cx"> Update the current data from the server.
</span><span class="cx"> """
</span><ins>+
</ins><span class="cx"> self.currentData = self.readSock(self.items)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -252,7 +288,8 @@
</span><span class="cx"> """
</span><span class="cx"> Update the current data from the server.
</span><span class="cx"> """
</span><del>- return self.readSock([item])[item]
</del><ins>+ data = self.readSock([item])
+ return data[item] if data else None
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def addItem(self, item):
</span><span class="lines">@@ -282,6 +319,8 @@
</span><span class="cx"> def __init__(self, usesCurses, client):
</span><span class="cx"> self.usesCurses = usesCurses
</span><span class="cx"> self.client = client
</span><ins>+ self.rowCount = 0
+ self.needsReset = False
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def makeWindow(self, top=0, left=0):
</span><span class="lines">@@ -315,6 +354,14 @@
</span><span class="cx"> return True
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def requiresReset(self):
+ """
+ Indicates that the window needs a full reset, because e.g., the
+ number of items it didplays has changed.
+ """
+ return self.needsReset
+
+
</ins><span class="cx"> def activate(self):
</span><span class="cx"> """
</span><span class="cx"> About to start displaying.
</span><span class="lines">@@ -342,7 +389,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def clientData(self):
</span><del>- return self.client.currentData[self.clientItem]
</del><ins>+ return self.client.currentData.get(self.clientItem)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def readItem(self, item):
</span><span class="lines">@@ -417,13 +464,17 @@
</span><span class="cx"> FORMAT_WIDTH = 78
</span><span class="cx">
</span><span class="cx"> def makeWindow(self, top=0, left=0):
</span><del>- nlines = self.readItem("jobcount")
- self._createWindow("Jobs", nlines + 5, ncols=self.FORMAT_WIDTH, begin_y=top, begin_x=left)
</del><ins>+ nlines = defaultIfNone(self.readItem("jobcount"), 0)
+ self.rowCount = nlines
+ self._createWindow("Jobs", self.rowCount + 5, ncols=self.FORMAT_WIDTH, begin_y=top, begin_x=left)
</ins><span class="cx"> return self
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def update(self):
</span><del>- records = self.clientData()
</del><ins>+ records = defaultIfNone(self.clientData(), {})
+ if len(records) != self.rowCount:
+ self.needsReset = True
+ return
</ins><span class="cx"> self.iter += 1
</span><span class="cx">
</span><span class="cx"> if self.usesCurses:
</span><span class="lines">@@ -496,20 +547,24 @@
</span><span class="cx">
</span><span class="cx"> help = "display server child job assignments"
</span><span class="cx"> clientItem = "job_assignments"
</span><del>- FORMAT_WIDTH = 32
</del><ins>+ FORMAT_WIDTH = 40
</ins><span class="cx">
</span><span class="cx"> def makeWindow(self, top=0, left=0):
</span><del>- slots = self.readItem(self.clientItem)["workers"]
</del><ins>+ slots = defaultIfNone(self.readItem(self.clientItem), {"workers": ()})["workers"]
+ self.rowCount = len(slots)
</ins><span class="cx"> self._createWindow(
</span><del>- "Job Assignments", len(slots) + 5, self.FORMAT_WIDTH,
</del><ins>+ "Job Assignments", self.rowCount + 5, self.FORMAT_WIDTH,
</ins><span class="cx"> begin_y=top, begin_x=left
</span><span class="cx"> )
</span><span class="cx"> return self
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def update(self):
</span><del>- data = self.clientData()
</del><ins>+ data = defaultIfNone(self.clientData(), {"workers": {}, "level": 0})
</ins><span class="cx"> records = data["workers"]
</span><ins>+ if len(records) != self.rowCount:
+ self.needsReset = True
+ return
</ins><span class="cx"> self.iter += 1
</span><span class="cx">
</span><span class="cx"> if self.usesCurses:
</span><span class="lines">@@ -522,25 +577,28 @@
</span><span class="cx">
</span><span class="cx"> x = 1
</span><span class="cx"> y = 1
</span><del>- s = " {:>4}{:>12}{:>12} ".format(
- "Slot", "assigned", "completed"
</del><ins>+ s = " {:>4}{:>12}{:>8}{:>12} ".format(
+ "Slot", "assigned", "load", "completed"
</ins><span class="cx"> )
</span><span class="cx"> if self.usesCurses:
</span><span class="cx"> self.window.addstr(y, x, s, curses.A_REVERSE)
</span><span class="cx"> else:
</span><span class="cx"> print(s)
</span><span class="cx"> y += 1
</span><ins>+ total_assigned = 0
</ins><span class="cx"> total_completed = 0
</span><span class="cx"> for ctr, details in enumerate(records):
</span><del>- assigned, completed = details
</del><ins>+ assigned, load, completed = details
+ total_assigned += assigned
</ins><span class="cx"> total_completed += completed
</span><span class="cx"> changed = (
</span><span class="cx"> ctr in self.lastResult and
</span><span class="cx"> self.lastResult[ctr] != assigned
</span><span class="cx"> )
</span><del>- s = " {:>4}{:>12}{:>12} ".format(
</del><ins>+ s = " {:>4}{:>12}{:>8}{:>12} ".format(
</ins><span class="cx"> ctr,
</span><span class="cx"> assigned,
</span><ins>+ load,
</ins><span class="cx"> completed,
</span><span class="cx"> )
</span><span class="cx"> try:
</span><span class="lines">@@ -555,8 +613,9 @@
</span><span class="cx"> pass
</span><span class="cx"> y += 1
</span><span class="cx">
</span><del>- s = " {:<6}{:>10}{:>12}".format(
</del><ins>+ s = " {:<6}{:>10}{:>8}{:>12}".format(
</ins><span class="cx"> "Total:",
</span><ins>+ total_assigned,
</ins><span class="cx"> "{}%".format(data["level"]),
</span><span class="cx"> total_completed,
</span><span class="cx"> )
</span><span class="lines">@@ -585,17 +644,21 @@
</span><span class="cx"> FORMAT_WIDTH = 72
</span><span class="cx">
</span><span class="cx"> def makeWindow(self, top=0, left=0):
</span><del>- slots = self.readItem(self.clientItem)["slots"]
</del><ins>+ slots = defaultIfNone(self.readItem(self.clientItem), {"slots": ()})["slots"]
+ self.rowCount = len(slots)
</ins><span class="cx"> self._createWindow(
</span><del>- "HTTP Slots", len(slots) + 5, self.FORMAT_WIDTH,
</del><ins>+ "HTTP Slots", self.rowCount + 5, self.FORMAT_WIDTH,
</ins><span class="cx"> begin_y=top, begin_x=left
</span><span class="cx"> )
</span><span class="cx"> return self
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def update(self):
</span><del>- data = self.clientData()
</del><ins>+ data = defaultIfNone(self.clientData(), {"slots": {}, "overloaded": False})
</ins><span class="cx"> records = data["slots"]
</span><ins>+ if len(records) != self.rowCount:
+ self.needsReset = True
+ return
</ins><span class="cx"> self.iter += 1
</span><span class="cx">
</span><span class="cx"> if self.usesCurses:
</span><span class="lines">@@ -688,13 +751,22 @@
</span><span class="cx"> clientItem = "stats_system"
</span><span class="cx">
</span><span class="cx"> def makeWindow(self, top=0, left=0):
</span><del>- slots = self.readItem(self.clientItem)
- self._createWindow("System", len(slots) + 3, begin_y=top, begin_x=left)
</del><ins>+ slots = defaultIfNone(self.readItem(self.clientItem), (1, 2, 3, 4,))
+ self.rowCount = len(slots)
+ self._createWindow("System", self.rowCount + 3, begin_y=top, begin_x=left)
</ins><span class="cx"> return self
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def update(self):
</span><del>- records = self.clientData()
</del><ins>+ records = defaultIfNone(self.clientData(), {
+ "cpu use": 0.0,
+ "memory percent": 0.0,
+ "memory used": 0,
+ "start time": time.time(),
+ })
+ if len(records) != self.rowCount:
+ self.needsReset = True
+ return
</ins><span class="cx"> self.iter += 1
</span><span class="cx">
</span><span class="cx"> if self.usesCurses:
</span><span class="lines">@@ -763,7 +835,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> def update(self):
</span><del>- records = self.clientData()
</del><ins>+ records = defaultIfNone(self.clientData(), {})
</ins><span class="cx"> self.iter += 1
</span><span class="cx">
</span><span class="cx"> if self.usesCurses:
</span><span class="lines">@@ -787,7 +859,15 @@
</span><span class="cx"> print(s2)
</span><span class="cx"> y += 2
</span><span class="cx"> for key, seconds in (("current", 60,), ("1m", 60,), ("5m", 5 * 60,), ("1h", 60 * 60,),):
</span><del>- stat = records[key]
</del><ins>+ stat = records.get(key, {
+ "requests": 0,
+ "t": 0.0,
+ "t-resp-wr": 0.0,
+ "T-MAX": 0.0,
+ "slots": 0,
+ "cpu": 0.0,
+ "500": 0,
+ })
</ins><span class="cx"> s = " {:<8}{:>8}{:>10.1f}{:>10.1f}{:>10.1f}{:>10.1f}{:>8.2f}{:>7.1f}%{:>8} ".format(
</span><span class="cx"> key,
</span><span class="cx"> stat["requests"],
</span></span></pre></div>
<a id="CalendarServertrunkrequirementsstabletxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/requirements-stable.txt (13483 => 13484)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/requirements-stable.txt        2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/requirements-stable.txt        2014-05-16 01:54:55 UTC (rev 13484)
</span><span class="lines">@@ -1,7 +1,7 @@
</span><span class="cx"> # For CalendarServer development, don't try to get these projects from PyPI; use svn.
</span><span class="cx">
</span><span class="cx"> -e .
</span><del>--e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13475#egg=twextpy
</del><ins>+-e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@13483#egg=twextpy
</ins><span class="cx"> -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
</span><span class="cx"> -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@13420#egg=pycalendar
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py (13483 => 13484)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py        2014-05-16 01:50:56 UTC (rev 13483)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py        2014-05-16 01:54:55 UTC (rev 13484)
</span><span class="lines">@@ -49,7 +49,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleWorkMixin(object):
</del><ins>+class ScheduleWorkMixin(WorkItem):
</ins><span class="cx"> """
</span><span class="cx"> Base class for common schedule work item behavior.
</span><span class="cx"> """
</span><span class="lines">@@ -131,7 +131,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleOrganizerWork(WorkItem, fromTable(schema.SCHEDULE_ORGANIZER_WORK), ScheduleWorkMixin):
</del><ins>+class ScheduleOrganizerWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_ORGANIZER_WORK)):
</ins><span class="cx"> """
</span><span class="cx"> The associated work item table is SCHEDULE_ORGANIZER_WORK.
</span><span class="cx">
</span><span class="lines">@@ -272,7 +272,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleReplyWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_WORK), ScheduleReplyWorkMixin):
</del><ins>+class ScheduleReplyWork(ScheduleReplyWorkMixin, fromTable(schema.SCHEDULE_REPLY_WORK)):
</ins><span class="cx"> """
</span><span class="cx"> The associated work item table is SCHEDULE_REPLY_WORK.
</span><span class="cx">
</span><span class="lines">@@ -353,7 +353,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleReplyCancelWork(WorkItem, fromTable(schema.SCHEDULE_REPLY_CANCEL_WORK), ScheduleReplyWorkMixin):
</del><ins>+class ScheduleReplyCancelWork(ScheduleReplyWorkMixin, fromTable(schema.SCHEDULE_REPLY_CANCEL_WORK)):
</ins><span class="cx"> """
</span><span class="cx"> The associated work item table is SCHEDULE_REPLY_CANCEL_WORK.
</span><span class="cx">
</span><span class="lines">@@ -416,7 +416,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleRefreshWork(WorkItem, fromTable(schema.SCHEDULE_REFRESH_WORK), ScheduleWorkMixin):
</del><ins>+class ScheduleRefreshWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_REFRESH_WORK)):
</ins><span class="cx"> """
</span><span class="cx"> The associated work item table is SCHEDULE_REFRESH_WORK.
</span><span class="cx">
</span><span class="lines">@@ -604,7 +604,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><del>-class ScheduleAutoReplyWork(WorkItem, fromTable(schema.SCHEDULE_AUTO_REPLY_WORK), ScheduleWorkMixin):
</del><ins>+class ScheduleAutoReplyWork(ScheduleWorkMixin, fromTable(schema.SCHEDULE_AUTO_REPLY_WORK)):
</ins><span class="cx"> """
</span><span class="cx"> The associated work item table is SCHEDULE_AUTO_REPLY_WORK.
</span><span class="cx">
</span></span></pre>
</div>
</div>
</body>
</html>