@@ -90,8 +90,10 @@ def __init__(self, hs, database):
9090 self ._clock = hs .get_clock ()
9191 self .db = database
9292
93+ # if a background update is currently running, its name.
94+ self ._current_background_update = None # type: Optional[str]
95+
9396 self ._background_update_performance = {}
94- self ._background_update_queue = []
9597 self ._background_update_handlers = {}
9698 self ._all_done = False
9799
@@ -111,34 +113,33 @@ async def run_background_updates(self, sleep=True):
111113 except Exception :
112114 logger .exception ("Error doing update" )
113115 else :
114- if result is None :
116+ if result :
115117 logger .info (
116118 "No more background updates to do."
117119 " Unscheduling background update task."
118120 )
119121 self ._all_done = True
120122 return None
121123
122- @defer .inlineCallbacks
123- def has_completed_background_updates (self ):
124+ async def has_completed_background_updates (self ) -> bool :
124125 """Check if all the background updates have completed
125126
126127 Returns:
127- Deferred[bool]: True if all background updates have completed
128+ True if all background updates have completed
128129 """
129130 # if we've previously determined that there is nothing left to do, that
130131 # is easy
131132 if self ._all_done :
132133 return True
133134
134- # obviously, if we have things in our queue , we're not done.
135- if self ._background_update_queue :
135+ # obviously, if we are currently processing an update , we're not done.
136+ if self ._current_background_update :
136137 return False
137138
138139 # otherwise, check if there are updates to be run. This is important,
139140 # as we may be running on a worker which doesn't perform the bg updates
140141 # itself, but still wants to wait for them to happen.
141- updates = yield self .db .simple_select_onecol (
142+ updates = await self .db .simple_select_onecol (
142143 "background_updates" ,
143144 keyvalues = None ,
144145 retcol = "1" ,
@@ -153,11 +154,10 @@ def has_completed_background_updates(self):
153154 async def has_completed_background_update (self , update_name ) -> bool :
154155 """Check if the given background update has finished running.
155156 """
156-
157157 if self ._all_done :
158158 return True
159159
160- if update_name in self ._background_update_queue :
160+ if update_name == self ._current_background_update :
161161 return False
162162
163163 update_exists = await self .db .simple_select_one_onecol (
@@ -170,9 +170,7 @@ async def has_completed_background_update(self, update_name) -> bool:
170170
171171 return not update_exists
172172
173- async def do_next_background_update (
174- self , desired_duration_ms : float
175- ) -> Optional [int ]:
173+ async def do_next_background_update (self , desired_duration_ms : float ) -> bool :
176174 """Does some amount of work on the next queued background update
177175
178176 Returns once some amount of work is done.
@@ -181,33 +179,51 @@ async def do_next_background_update(
181179 desired_duration_ms(float): How long we want to spend
182180 updating.
183181 Returns:
184- None if there is no more work to do , otherwise an int
182+ True if we have finished running all the background updates , otherwise False
185183 """
186- if not self ._background_update_queue :
187- updates = await self .db .simple_select_list (
188- "background_updates" ,
189- keyvalues = None ,
190- retcols = ("update_name" , "depends_on" ),
184+
185+ def get_background_updates_txn (txn ):
186+ txn .execute (
187+ """
188+ SELECT update_name, depends_on FROM background_updates
189+ ORDER BY ordering, update_name
190+ """
191191 )
192- in_flight = {update ["update_name" ] for update in updates }
193- for update in updates :
194- if update ["depends_on" ] not in in_flight :
195- self ._background_update_queue .append (update ["update_name" ])
192+ return self .db .cursor_to_dict (txn )
196193
197- if not self ._background_update_queue :
198- # no work left to do
199- return None
194+ if not self ._current_background_update :
195+ all_pending_updates = await self .db .runInteraction (
196+ "background_updates" , get_background_updates_txn ,
197+ )
198+ if not all_pending_updates :
199+ # no work left to do
200+ return True
201+
202+ # find the first update which isn't dependent on another one in the queue.
203+ pending = {update ["update_name" ] for update in all_pending_updates }
204+ for upd in all_pending_updates :
205+ depends_on = upd ["depends_on" ]
206+ if not depends_on or depends_on not in pending :
207+ break
208+ logger .info (
209+ "Not starting on bg update %s until %s is done" ,
210+ upd ["update_name" ],
211+ depends_on ,
212+ )
213+ else :
214+ # if we get to the end of that for loop, there is a problem
215+ raise Exception (
216+ "Unable to find a background update which doesn't depend on "
217+ "another: dependency cycle?"
218+ )
200219
201- # pop from the front, and add back to the back
202- update_name = self ._background_update_queue .pop (0 )
203- self ._background_update_queue .append (update_name )
220+ self ._current_background_update = upd ["update_name" ]
204221
205- res = await self ._do_background_update (update_name , desired_duration_ms )
206- return res
222+ await self ._do_background_update (desired_duration_ms )
223+ return False
207224
208- async def _do_background_update (
209- self , update_name : str , desired_duration_ms : float
210- ) -> int :
225+ async def _do_background_update (self , desired_duration_ms : float ) -> int :
226+ update_name = self ._current_background_update
211227 logger .info ("Starting update batch on background update '%s'" , update_name )
212228
213229 update_handler = self ._background_update_handlers [update_name ]
@@ -400,27 +416,6 @@ def updater(progress, batch_size):
400416
401417 self .register_background_update_handler (update_name , updater )
402418
403- def start_background_update (self , update_name , progress ):
404- """Starts a background update running.
405-
406- Args:
407- update_name: The update to set running.
408- progress: The initial state of the progress of the update.
409-
410- Returns:
411- A deferred that completes once the task has been added to the
412- queue.
413- """
414- # Clear the background update queue so that we will pick up the new
415- # task on the next iteration of do_background_update.
416- self ._background_update_queue = []
417- progress_json = json .dumps (progress )
418-
419- return self .db .simple_insert (
420- "background_updates" ,
421- {"update_name" : update_name , "progress_json" : progress_json },
422- )
423-
424419 def _end_background_update (self , update_name ):
425420 """Removes a completed background update task from the queue.
426421
@@ -429,9 +424,12 @@ def _end_background_update(self, update_name):
429424 Returns:
430425 A deferred that completes once the task is removed.
431426 """
432- self ._background_update_queue = [
433- name for name in self ._background_update_queue if name != update_name
434- ]
427+ if update_name != self ._current_background_update :
428+ raise Exception (
429+ "Cannot end background update %s which isn't currently running"
430+ % update_name
431+ )
432+ self ._current_background_update = None
435433 return self .db .simple_delete_one (
436434 "background_updates" , keyvalues = {"update_name" : update_name }
437435 )
0 commit comments