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:
For reference, `xonfig` says:

```
+------------------+-----------------+
| xonsh            | 0.6.3           |
| Python           | 3.6.5           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 1.0.15          |
| shell type       | prompt_toolkit  |
| pygments         | 2.2.0           |
| on posix         | True            |
| on linux         | True            |
| distro           | debian          |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| on msys2         | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
+------------------+-----------------+
```
Thanks for reporting @dratini0!  I can confirm this behavior.  
> My suspicion is that this is due to the fact that exit handlers are not ran when exec is used.

Really? I thought that atexit was always run...
This still is bugged fyi
For others wishing to move to SQLite, use this once:

[How to convert history to sqlite?](https://github.com/xonsh/xonsh/discussions/4132)


```python
from xonsh.history.json import JsonHistory
from xonsh.history.sqlite import SqliteHistory

class History(SqliteHistory):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.clear()

        jh = JsonHistory(**kwargs)
        for item in jh.all_items():
            self.append(item)
```

Resolving patch:
diff --git a/xonsh/built_ins.py b/xonsh/built_ins.py
--- a/xonsh/built_ins.py
+++ b/xonsh/built_ins.py
@@ -686,6 +686,10 @@ def unload(self):
 
         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
--- 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()