Merge branch 'fix/fileAccessRaceCondition' into maintenance

This commit is contained in:
Gina Häußge 2017-03-13 17:14:14 +01:00
commit 1b91790f5f

View file

@ -392,7 +392,7 @@ class MachineCom(object):
self._currentFile = None
# multithreading locks
self._sendNextLock = threading.Lock()
self._jobLock = threading.RLock()
self._sendingLock = threading.RLock()
# monitoring thread
@ -748,41 +748,42 @@ class MachineCom(object):
self._pauseWaitTimeLost = 0.0
try:
self._currentFile.start()
with self._jobLock:
self._currentFile.start()
self._changeState(self.STATE_PRINTING)
self._changeState(self.STATE_PRINTING)
self.resetLineNumbers()
self.resetLineNumbers()
self._callback.on_comm_print_job_started()
self._callback.on_comm_print_job_started()
if self.isSdFileSelected():
#self.sendCommand("M26 S0") # setting the sd pos apparently sometimes doesn't work, so we re-select
# the file instead
if self.isSdFileSelected():
#self.sendCommand("M26 S0") # setting the sd pos apparently sometimes doesn't work, so we re-select
# the file instead
# make sure to ignore the "file selected" later on, otherwise we'll reset our progress data
self._ignore_select = True
self.sendCommand("M23 {filename}".format(filename=self._currentFile.getFilename()))
if pos is not None and isinstance(pos, int) and pos > 0:
self._currentFile.setFilepos(pos)
self.sendCommand("M26 S{}".format(pos))
# make sure to ignore the "file selected" later on, otherwise we'll reset our progress data
self._ignore_select = True
self.sendCommand("M23 {filename}".format(filename=self._currentFile.getFilename()))
if pos is not None and isinstance(pos, int) and pos > 0:
self._currentFile.setFilepos(pos)
self.sendCommand("M26 S{}".format(pos))
else:
self._currentFile.setFilepos(0)
self.sendCommand("M24")
self._sd_status_timer = RepeatedTimer(self._timeout_intervals.get("sdStatus", 1.0), self._poll_sd_status, run_first=True)
self._sd_status_timer.start()
else:
self._currentFile.setFilepos(0)
if pos is not None and isinstance(pos, int) and pos > 0:
self._currentFile.seek(pos)
self.sendCommand("M24")
line = self._getNext()
if line is not None:
self.sendCommand(line)
self._sd_status_timer = RepeatedTimer(self._timeout_intervals.get("sdStatus", 1.0), self._poll_sd_status, run_first=True)
self._sd_status_timer.start()
else:
if pos is not None and isinstance(pos, int) and pos > 0:
self._currentFile.seek(pos)
line = self._getNext()
if line is not None:
self.sendCommand(line)
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue()
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue()
except:
self._logger.exception("Error while trying to start printing")
self._errorValue = get_exception_string()
@ -791,17 +792,18 @@ class MachineCom(object):
def startFileTransfer(self, filename, localFilename, remoteFilename):
if not self.isOperational() or self.isBusy():
logging.info("Printer is not operation or busy")
logging.info("Printer is not operational or busy")
return
self.resetLineNumbers()
with self._jobLock:
self.resetLineNumbers()
self._currentFile = StreamingGcodeFileInformation(filename, localFilename, remoteFilename)
self._currentFile.start()
self._currentFile = StreamingGcodeFileInformation(filename, localFilename, remoteFilename)
self._currentFile.start()
self.sendCommand("M28 %s" % remoteFilename)
eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename})
self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize())
self.sendCommand("M28 %s" % remoteFilename)
eventManager().fire(Events.TRANSFER_STARTED, {"local": localFilename, "remote": remoteFilename})
self._callback.on_comm_file_transfer_started(remoteFilename, self._currentFile.getFilesize())
def selectFile(self, filename, sd):
if self.isBusy():
@ -840,18 +842,6 @@ class MachineCom(object):
# we aren't even printing, nothing to cancel...
return
self._changeState(self.STATE_OPERATIONAL)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print
self.sendCommand("M27") # get current byte position in file
self.sendCommand("M26 S0") # reset position in file to byte 0
if self._sd_status_timer is not None:
try:
self._sd_status_timer.cancel()
except:
pass
def _on_M400_sent():
# we don't call on_print_job_cancelled on our callback here
# because we do this only after our M114 has been answered
@ -859,7 +849,20 @@ class MachineCom(object):
self._record_cancel_position = True
self.sendCommand("M114")
self.sendCommand("M400", on_sent=_on_M400_sent)
with self._jobLock:
self._changeState(self.STATE_OPERATIONAL)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print
self.sendCommand("M27") # get current byte position in file
self.sendCommand("M26 S0") # reset position in file to byte 0
if self._sd_status_timer is not None:
try:
self._sd_status_timer.cancel()
except:
pass
self.sendCommand("M400", on_sent=_on_M400_sent)
def _pause_preparation_done(self):
self._callback.on_comm_print_job_paused()
@ -871,41 +874,42 @@ class MachineCom(object):
if not self._currentFile:
return
if not pause and self.isPaused():
if self._pauseWaitStartTime:
self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime)
self._pauseWaitStartTime = None
with self._jobLock:
if not pause and self.isPaused():
if self._pauseWaitStartTime:
self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime)
self._pauseWaitStartTime = None
self._changeState(self.STATE_PRINTING)
self._callback.on_comm_print_job_resumed()
self._changeState(self.STATE_PRINTING)
self._callback.on_comm_print_job_resumed()
if self.isSdFileSelected():
self.sendCommand("M24")
self.sendCommand("M27")
else:
line = self._getNext()
if line is not None:
self.sendCommand(line)
if self.isSdFileSelected():
self.sendCommand("M24")
self.sendCommand("M27")
else:
line = self._getNext()
if line is not None:
self.sendCommand(line)
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue()
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue()
elif pause and self.isPrinting():
if not self._pauseWaitStartTime:
self._pauseWaitStartTime = time.time()
elif pause and self.isPrinting():
if not self._pauseWaitStartTime:
self._pauseWaitStartTime = time.time()
self._changeState(self.STATE_PAUSED)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print
self._changeState(self.STATE_PAUSED)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print
def _on_M400_sent():
# we don't call on_print_job_paused on our callback here
# because we do this only after our M114 has been answered
# by the firmware
self._record_pause_position = True
self.sendCommand("M114")
def _on_M400_sent():
# we don't call on_print_job_paused on our callback here
# because we do this only after our M114 has been answered
# by the firmware
self._record_pause_position = True
self.sendCommand("M114")
self.sendCommand("M400", on_sent=_on_M400_sent)
self.sendCommand("M400", on_sent=_on_M400_sent)
def getSdFiles(self):
return self._sdFiles
@ -1843,7 +1847,7 @@ class MachineCom(object):
return line
def _sendNext(self):
with self._sendNextLock:
with self._jobLock:
while self._active:
# we loop until we've actually enqueued a line for sending
if self._state != self.STATE_PRINTING:
@ -2607,6 +2611,7 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
PrintingFileInformation.__init__(self, filename)
self._handle = None
self._handle_mutex = threading.RLock()
self._offsets_callback = offsets_callback
self._current_tool_callback = current_tool_callback
@ -2618,76 +2623,80 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
self._read_lines = 0
def seek(self, offset):
if self._handle is None:
return
with self._handle_mutex:
if self._handle is None:
return
self._handle.seek(offset)
self._pos = self._handle.tell()
self._read_lines = 0
self._handle.seek(offset)
self._pos = self._handle.tell()
self._read_lines = 0
def start(self):
"""
Opens the file for reading and determines the file size.
"""
PrintingFileInformation.start(self)
self._handle = bom_aware_open(self._filename, encoding="utf-8", errors="replace")
self._pos = self._handle.tell()
if self._handle.encoding.endswith("-sig"):
# Apparently we found an utf-8 bom in the file.
# We need to add its length to our pos because it will
# be stripped transparently and we'll have no chance
# catching that.
import codecs
self._pos += len(codecs.BOM_UTF8)
self._read_lines = 0
with self._handle_mutex:
self._handle = bom_aware_open(self._filename, encoding="utf-8", errors="replace")
self._pos = self._handle.tell()
if self._handle.encoding.endswith("-sig"):
# Apparently we found an utf-8 bom in the file.
# We need to add its length to our pos because it will
# be stripped transparently and we'll have no chance
# catching that.
import codecs
self._pos += len(codecs.BOM_UTF8)
self._read_lines = 0
def close(self):
"""
Closes the file if it's still open.
"""
PrintingFileInformation.close(self)
if self._handle is not None:
try:
self._handle.close()
except:
pass
self._handle = None
with self._handle_mutex:
if self._handle is not None:
try:
self._handle.close()
except:
pass
self._handle = None
def getNext(self):
"""
Retrieves the next line for printing.
"""
if self._handle is None:
raise ValueError("File %s is not open for reading" % self._filename)
with self._handle_mutex:
if self._handle is None:
raise ValueError("File %s is not open for reading" % self._filename)
try:
offsets = self._offsets_callback() if self._offsets_callback is not None else None
current_tool = self._current_tool_callback() if self._current_tool_callback is not None else None
try:
offsets = self._offsets_callback() if self._offsets_callback is not None else None
current_tool = self._current_tool_callback() if self._current_tool_callback is not None else None
processed = None
while processed is None:
if self._handle is None:
# file got closed just now
self._pos = self._size
self._report_stats()
return None
processed = None
while processed is None:
if self._handle is None:
# file got closed just now
self._pos = self._size
self._report_stats()
return None
# we need to manually keep track of our pos here since
# codecs' readline will make our handle's tell not
# return the actual number of bytes read, but also the
# already buffered bytes (for detecting the newlines)
line = self._handle.readline()
self._pos += len(line.encode("utf-8"))
# we need to manually keep track of our pos here since
# codecs' readline will make our handle's tell not
# return the actual number of bytes read, but also the
# already buffered bytes (for detecting the newlines)
line = self._handle.readline()
self._pos += len(line.encode("utf-8"))
if not line:
self.close()
processed = self._process(line, offsets, current_tool)
self._read_lines += 1
return processed
except Exception as e:
self.close()
self._logger.exception("Exception while processing line")
raise e
if not line:
self.close()
processed = self._process(line, offsets, current_tool)
self._read_lines += 1
return processed
except Exception as e:
self.close()
self._logger.exception("Exception while processing line")
raise e
def _process(self, line, offsets, current_tool):
return process_gcode_line(line, offsets=offsets, current_tool=current_tool)