Repository: xonsh/xonsh

Issue:
Save history when the shell is exiting abnormally
When using the JSON backend, the history is not saved unless the shell is exited properly (for example, by ^D). When the `exec` alias is used, or a crash is induced (by for example adding invalid colors into `$BOTTOM_TOOLBAR`), the history file is not saved, and is left locked without commands being written into it.

My suspicion is that this is due to the fact that exit  handlers are not ran when exec is used. This doesn't happen with SQLite for obvious reasons.

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**


Discussion:

Patch:
diff --git a/news/history_save_unload.rst b/news/history_save_unload.rst
new file mode 100644
index 00000000..be1beacf
--- /dev/null
+++ b/news/history_save_unload.rst
@@ -0,0 +1,23 @@
+**Added:**
+
+* Saving history in case of any type of exiting the shell.
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* <news item>
+
+**Security:**
+
+* <news item>
diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py
index 0ad7e93c..d52a6ca6 100644
--- a/xonsh/built_ins.py
+++ b/xonsh/built_ins.py
@@ -691,6 +691,10 @@ class XonshSession:
 
         if not self.builtins_loaded:
             return
+
+        if self.history is not None:
+            self.history.flush(at_exit=True)
+
         self.unlink_builtins()
         delattr(builtins, "__xonsh__")
         self.builtins_loaded = False


Test patch:
diff --git a/tests/test_history_json.py b/tests/test_history_json.py
index ff05dd4c..20dcfca6 100644
--- a/tests/test_history_json.py
+++ b/tests/test_history_json.py
@@ -70,6 +70,18 @@ def test_hist_flush(hist, xession):
         assert not cmd.get("out", None)
 
 
+def test_hist_flush_on_xonsh_unload(hist, xession):
+    hf = hist.flush()
+    assert hf is None
+    xession.env["HISTCONTROL"] = set()
+    hist.append({"inp": "save me if you unload", "rtn": 0, "out": "yes"})
+    xession.unload()
+    with LazyJSON(hist.filename) as lj:
+        assert len(lj["cmds"]) == 1
+        cmd = lj["cmds"][0]
+        assert cmd["inp"] == "save me if you unload"
+
+
 def test_hist_flush_with_store_stdout(hist, xession):
     """Verify explicit flushing of the history works."""
     hf = hist.flush()