(self, must_create=False)
| 121 | return self.create() |
| 122 | |
| 123 | def save(self, must_create=False): |
| 124 | if self.session_key is None: |
| 125 | return self.create() |
| 126 | # Get the session data now, before we start messing |
| 127 | # with the file it is stored within. |
| 128 | session_data = self._get_session(no_load=must_create) |
| 129 | |
| 130 | session_file_name = self._key_to_file() |
| 131 | |
| 132 | try: |
| 133 | # Make sure the file exists. If it does not already exist, an |
| 134 | # empty placeholder file is created. |
| 135 | flags = os.O_WRONLY | getattr(os, "O_BINARY", 0) |
| 136 | if must_create: |
| 137 | flags |= os.O_EXCL | os.O_CREAT |
| 138 | fd = os.open(session_file_name, flags) |
| 139 | os.close(fd) |
| 140 | except FileNotFoundError: |
| 141 | if not must_create: |
| 142 | raise UpdateError |
| 143 | except FileExistsError: |
| 144 | if must_create: |
| 145 | raise CreateError |
| 146 | |
| 147 | # Write the session file without interfering with other threads |
| 148 | # or processes. By writing to an atomically generated temporary |
| 149 | # file and then using the atomic os.rename() to make the complete |
| 150 | # file visible, we avoid having to lock the session file, while |
| 151 | # still maintaining its integrity. |
| 152 | # |
| 153 | # Note: Locking the session file was explored, but rejected in part |
| 154 | # because in order to be atomic and cross-platform, it required a |
| 155 | # long-lived lock file for each session, doubling the number of |
| 156 | # files in the session storage directory at any given time. This |
| 157 | # rename solution is cleaner and avoids any additional overhead |
| 158 | # when reading the session data, which is the more common case |
| 159 | # unless SESSION_SAVE_EVERY_REQUEST = True. |
| 160 | # |
| 161 | # See ticket #8616. |
| 162 | dir, prefix = os.path.split(session_file_name) |
| 163 | |
| 164 | try: |
| 165 | output_file_fd, output_file_name = tempfile.mkstemp( |
| 166 | dir=dir, prefix=prefix + "_out_" |
| 167 | ) |
| 168 | renamed = False |
| 169 | try: |
| 170 | try: |
| 171 | os.write(output_file_fd, self.encode(session_data).encode()) |
| 172 | finally: |
| 173 | os.close(output_file_fd) |
| 174 | |
| 175 | # This will atomically rename the file (os.rename) if the OS |
| 176 | # supports it. Otherwise this will result in a shutil.copy2 |
| 177 | # and os.unlink (for example on Windows). See #9084. |
| 178 | shutil.move(output_file_name, session_file_name) |
| 179 | renamed = True |
| 180 | finally: |
no test coverage detected