##// END OF EJS Templates
Put history saving into a separate thread.
Thomas Kluyver -
Show More
@@ -13,11 +13,13 b''
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 import atexit
16 17 import datetime
17 18 import json
18 19 import os
19 20 import re
20 21 import sqlite3
22 import threading
21 23
22 24 from collections import defaultdict
23 25
@@ -76,6 +78,10 b' class HistoryManager(Configurable):'
76 78 db_input_cache = List()
77 79 db_output_cache = List()
78 80
81 # History saving in separate thread
82 save_thread = Instance('IPython.core.history.HistorySavingThread')
83 save_flag = Instance(threading._Event)
84
79 85 # Private interface
80 86 # Variables used to store the three last inputs from the user. On each new
81 87 # history update, we populate the user's namespace with these, shifted as
@@ -119,6 +125,10 b' class HistoryManager(Configurable):'
119 125 else:
120 126 # The hist_file is probably :memory: or something else.
121 127 raise
128
129 self.save_flag = threading.Event()
130 self.save_thread = HistorySavingThread(self)
131 self.save_thread.start()
122 132
123 133 self.new_session()
124 134
@@ -376,7 +386,7 b' class HistoryManager(Configurable):'
376 386 self.db_input_cache.append((line_num, source, source_raw))
377 387 # Trigger to flush cache and write to DB.
378 388 if len(self.db_input_cache) >= self.db_cache_size:
379 self.writeout_cache()
389 self.save_flag.set()
380 390
381 391 # update the auto _i variables
382 392 self._iii = self._ii
@@ -408,44 +418,69 b' class HistoryManager(Configurable):'
408 418
409 419 self.db_output_cache.append((line_num, output))
410 420 if self.db_cache_size <= 1:
411 self.writeout_cache()
412
413 def _writeout_input_cache(self):
414 with self.db:
421 self.save_flag.set()
422
423 def _writeout_input_cache(self, conn):
424 with conn:
415 425 for line in self.db_input_cache:
416 self.db.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
426 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
417 427 (self.session_number,)+line)
418 428
419 def _writeout_output_cache(self):
420 with self.db:
429 def _writeout_output_cache(self, conn):
430 with conn:
421 431 for line in self.db_output_cache:
422 self.db.execute("INSERT INTO output_history VALUES (?, ?, ?)",
432 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
423 433 (self.session_number,)+line)
424 434
425 def writeout_cache(self):
435 def writeout_cache(self, conn=None):
426 436 """Write any entries in the cache to the database."""
437 if conn is None:
438 conn = self.db
427 439 try:
428 self._writeout_input_cache()
440 self._writeout_input_cache(conn)
429 441 except sqlite3.IntegrityError:
430 442 self.new_session()
431 443 print("ERROR! Session/line number was not unique in",
432 444 "database. History logging moved to new session",
433 445 self.session_number)
434 446 try: # Try writing to the new session. If this fails, don't recurse
435 self._writeout_input_cache()
447 self._writeout_input_cache(conn)
436 448 except sqlite3.IntegrityError:
437 449 pass
438 450 finally:
439 451 self.db_input_cache = []
440 452
441 453 try:
442 self._writeout_output_cache()
454 self._writeout_output_cache(conn)
443 455 except sqlite3.IntegrityError:
444 456 print("!! Session/line number for output was not unique",
445 457 "in database. Output will not be stored.")
446 458 finally:
447 459 self.db_output_cache = []
448 460
461
462 class HistorySavingThread(threading.Thread):
463 daemon = True
464 stop_now = False
465 def __init__(self, history_manager):
466 super(HistorySavingThread, self).__init__()
467 self.history_manager = history_manager
468 atexit.register(self.stop)
469
470 def run(self):
471 # We need a separate db connection per thread:
472 self.db = sqlite3.connect(self.history_manager.hist_file)
473 while True:
474 self.history_manager.save_flag.wait()
475 self.history_manager.save_flag.clear()
476 self.history_manager.writeout_cache(self.db)
477 if self.stop_now:
478 return
479
480 def stop(self):
481 self.stop_now = True
482 self.history_manager.save_flag.set()
483
449 484
450 485 # To match, e.g. ~5/8-~2/3
451 486 range_re = re.compile(r"""
General Comments 0
You need to be logged in to leave comments. Login now