|
12 | 12 | """
|
13 | 13 |
|
14 | 14 | # Python imports
|
| 15 | +import os |
15 | 16 | import pickle
|
| 17 | +import tempfile |
16 | 18 |
|
17 | 19 | # Local imports
|
18 | 20 | from . import token
|
@@ -86,8 +88,39 @@ def __init__(self):
|
86 | 88 |
|
87 | 89 | def dump(self, filename):
|
88 | 90 | """Dump the grammar tables to a pickle file."""
|
89 |
| - with open(filename, 'wb') as f: |
90 |
| - pickle.dump(self.__dict__, f, pickle.HIGHEST_PROTOCOL) |
| 91 | + # NOTE: |
| 92 | + # - We're writing a tempfile first so that there is no chance |
| 93 | + # for someone to read a half-written file from this very spot |
| 94 | + # while we're were not done writing. |
| 95 | + # - We're using ``os.rename`` to sure not copy data around (which |
| 96 | + # would get us back to square one with a reading-half-written file |
| 97 | + # race condition). |
| 98 | + # - We're making the tempfile go to the same directory as the eventual |
| 99 | + # target ``filename`` so that there is no chance of failing from |
| 100 | + # cross-file-system renames in ``os.rename``. |
| 101 | + # - We're using the same prefix and suffix for the tempfile so if we |
| 102 | + # ever have to leave a tempfile around for failure of deletion, |
| 103 | + # it will have a reasonable filename extension and its name will help |
| 104 | + # explain is nature. |
| 105 | + tempfile_dir = os.path.dirname(filename) |
| 106 | + tempfile_prefix, tempfile_suffix = os.path.splitext(filename) |
| 107 | + with tempfile.NamedTemporaryFile( |
| 108 | + mode='wb', |
| 109 | + suffix=tempfile_suffix, |
| 110 | + prefix=tempfile_prefix, |
| 111 | + dir=tempfile_dir, |
| 112 | + delete=False) as f: |
| 113 | + pickle.dump(self.__dict__, f.file, pickle.HIGHEST_PROTOCOL) |
| 114 | + try: |
| 115 | + os.rename(f.name, filename) |
| 116 | + except OSError: |
| 117 | + # This makes sure that we do not leave the tempfile around |
| 118 | + # unless we have to... |
| 119 | + try: |
| 120 | + os.remove(f.name) |
| 121 | + except OSError: |
| 122 | + pass |
| 123 | + raise |
91 | 124 |
|
92 | 125 | def load(self, filename):
|
93 | 126 | """Load the grammar tables from a pickle file."""
|
|
0 commit comments