5 Commits 226e28c114 ... b6e2a8cf76

Author SHA1 Message Date
  Michael Buesch b6e2a8cf76 coreserver: Properly set scheduling policy for peripheral threads 5 years ago
  Michael Buesch c635d2cc79 coreserver: Report fatal exceptions to the client 5 years ago
  Michael Buesch c6abb77d8e coreserver: Set affinity to peripheral before starting hw 5 years ago
  Michael Buesch 47e8e8f390 core: Add method for hardware startup 5 years ago
  Michael Buesch 3ca9d711ab profiler: Add support for multiple activations 5 years ago
5 changed files with 189 additions and 112 deletions
  1. 7 0
      awlsim/common/exceptions.py
  2. 9 3
      awlsim/common/profiler.py
  3. 1 0
      awlsim/core/main.pxd.in
  4. 14 3
      awlsim/core/main.py
  5. 158 106
      awlsim/coreserver/server.py

+ 7 - 0
awlsim/common/exceptions.py

@@ -58,6 +58,7 @@ class AwlSimError(Exception):
 		self.sourceId = sourceId
 		self.sourceName = sourceName
 		self.seenByUser = False
+		self.reportOnly = False
 
 	def getMessage(self):
 		return self.message
@@ -228,6 +229,12 @@ class AwlSimError(Exception):
 	def setSeenByUser(self, seen=True):
 		self.seenByUser = seen
 
+	def getReportOnlyFlag(self):
+		return self.reportOnly
+
+	def setReportOnlyFlag(self, reportOnly):
+		self.reportOnly = reportOnly
+
 	def __repr__(self):
 		return self.getReport()
 

+ 9 - 3
awlsim/common/profiler.py

@@ -2,7 +2,7 @@
 #
 # AWL simulator - Profiler support
 #
-# Copyright 2012-2018 Michael Buesch <m@bues.ch>
+# Copyright 2012-2019 Michael Buesch <m@bues.ch>
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -37,6 +37,7 @@ class Profiler(object):
 		"__profileModule",
 		"__pstatsModule",
 		"__profiler",
+		"__enableCount",
 	)
 
 	def __init__(self):
@@ -57,12 +58,17 @@ class Profiler(object):
 				"Cannot enable profiling.")
 
 		self.__profiler = self.__profileModule.Profile()
+		self.__enableCount = 0
 
 	def start(self):
-		self.__profiler.enable()
+		if self.__enableCount <= 0:
+			self.__profiler.enable()
+		self.__enableCount += 1
 
 	def stop(self):
-		self.__profiler.disable()
+		self.__enableCount = max(self.__enableCount - 1, 0)
+		if self.__enableCount <= 0:
+			self.__profiler.disable()
 
 	def getResult(self):
 		sio = StringIO()

+ 1 - 0
awlsim/core/main.pxd.in

@@ -6,6 +6,7 @@ cdef class AwlSim(object):
 	cdef public _Bool _fatalHwErrors
 	cdef public list __registeredHardware
 	cdef public uint32_t __registeredHardwareCount
+	cdef public _Bool __hwStartupRequired
 
 	cdef public int32_t _profileLevel
 	cdef public object __profileModule

+ 14 - 3
awlsim/core/main.py

@@ -80,6 +80,7 @@ class AwlSim(object): #+cdef
 	def __init__(self):
 		self.__registeredHardware = []
 		self.__registeredHardwareCount = 0
+		self.__hwStartupRequired = True
 		self._fatalHwErrors = True
 		self.cpu = S7CPU()
 		self.cpu.setPeripheralReadCallback(self.__peripheralReadCallback)
@@ -180,7 +181,13 @@ class AwlSim(object): #+cdef
 	@profiled(2)
 	@throwsAwlSimError
 	def startup(self):
-		self.__hwStartup()
+		# Startup the hardware modules, if required.
+		if self.__hwStartupRequired:
+			self.hardwareStartup()
+		# Next time we need a startup again.
+		self.__hwStartupRequired = True
+
+		# Startup the CPU core.
 		try:
 			self.__readHwInputs()
 			self.cpu.startup()
@@ -240,6 +247,7 @@ class AwlSim(object): #+cdef
 		if hwClassInst.getParamValueByName("enabled"):
 			self.__registeredHardware.append(hwClassInst)
 			self.__registeredHardwareCount = len(self.__registeredHardware)
+			self.__hwStartupRequired = True
 
 	def registerHardwareClass(self, hwClass, parameters={}):
 		"""Register a new hardware interface class.
@@ -259,7 +267,9 @@ class AwlSim(object): #+cdef
 
 		return HwModLoader.loadModule(name).getInterface()
 
-	def __hwStartup(self):
+	@profiled(2)
+	@throwsAwlSimError
+	def hardwareStartup(self):
 		"""Startup all attached hardware modules.
 		"""
 
@@ -272,7 +282,8 @@ class AwlSim(object): #+cdef
 				hw.startup()
 			except AwlSimError as e:
 				# Always fatal in startup.
-				self._handleSimException(e, fatal = True)
+				self._handleSimException(e, fatal=True)
+		self.__hwStartupRequired = False
 
 #@cy	@cython.boundscheck(False)
 	def __readHwInputs(self): #+cdef

+ 158 - 106
awlsim/coreserver/server.py

@@ -403,9 +403,13 @@ class AwlSimServer(object): #+cdef
 			printError("Cannot set CPU affinity. "
 				   "os.sched_setaffinity is not available.")
 
-	def __setSched(self, allowRtPolicy=True):
+	def __setSched(self, allowRtPolicy=False, peripheral=False):
 		"""Set the scheduling policy and priority to what is set by
-		AWLSIM_SCHED and AWLSIM_PRIO environment variable, if enable==True.
+		AWLSIM_SCHED and AWLSIM_PRIO environment variable.
+		If allowRtPolicy is False, then don't allow any realtime policy
+		and fall back to non-realtime.
+		If peripheral is True, then don't allow policies unsuitable for
+		peripheral threads. Fall back to sane alternatives.
 		"""
 		self.__rtSchedEnabled = False
 
@@ -413,19 +417,31 @@ class AwlSimServer(object): #+cdef
 		if (sched is not None and
 		    sched != AwlSimEnv.SCHED_DEFAULT):
 			if not allowRtPolicy:
+				# Realtime is not allowed.
+				# Fall back to NORMAL.
 				sched = AwlSimEnv.SCHED_NORMAL
 			policy = None
 			if sched == AwlSimEnv.SCHED_NORMAL:
 				policy = getattr(os, "SCHED_OTHER", None)
 				isRealtime = False
 			elif sched == AwlSimEnv.SCHED_FIFO:
-				policy = getattr(os, "SCHED_FIFO", None)
+				if peripheral:
+					# No FIFO for peripheral threads.
+					# Use RR instead.
+					policy = getattr(os, "SCHED_RR", None)
+				else:
+					policy = getattr(os, "SCHED_FIFO", None)
 				isRealtime = True
 			elif sched == AwlSimEnv.SCHED_RR:
 				policy = getattr(os, "SCHED_RR", None)
 				isRealtime = True
 			elif sched == AwlSimEnv.SCHED_DEADLINE:
-				policy = getattr(os, "SCHED_DEADLINE", None)
+				if peripheral:
+					# No DEADLINE for peripheral threads.
+					# Use RR instead.
+					policy = getattr(os, "SCHED_RR", None)
+				else:
+					policy = getattr(os, "SCHED_DEADLINE", None)
 				isRealtime = True
 				policy = None #TODO we also need to set the deadline scheduling parameters.
 			if policy is None: #@nocov
@@ -527,67 +543,94 @@ class AwlSimServer(object): #+cdef
 			# We are exiting. Cannot set another state.
 			return
 
-		if runstate == self.STATE_RUN or\
-		   runstate == self.STATE_STOP:
-			# Reset instruction state dump.
-			self.__insnSerial = 0
-			for client in self.__clients:
-				for insnStateDump in dictValues(client.insnStateDump):
-					insnStateDump.msgs = []
-
-		if runstate == self._STATE_INIT:
-			# We just entered initialization state.
-			printVerbose("Putting CPU into INIT state.")
-			self.__setSched(False)
-			self.__needOB10x = True
-		elif runstate == self.STATE_RUN:
-			# We just entered RUN state.
-			self.__setSched(True)
-			self.__startupTimeStamp = monotonic_time()
-			if self.__needOB10x:
-				printVerbose("CPU startup (OB 10x).")
-				self.__sim.startup()
-				self.__needOB10x = False
-			printVerbose("Putting CPU into RUN state.")
-		elif runstate == self.STATE_STOP:
-			# We just entered STOP state.
-			printVerbose("Putting CPU into STOP state.")
-			self.__setSched(False)
-		elif runstate == self.STATE_MAINTENANCE:
-			# We just entered MAINTENANCE state.
-			printVerbose("Putting CPU into MAINTENANCE state.")
-			self.__setSched(False)
-			self.__needOB10x = True
-		else:
-			self.__setSched(False)
-
-		# Select garbage collection mode.
-		if self.__gc_collect and self.__gc_get_count:
-			# If we are in RUN state with realtime scheduling,
-			# use manual garbage collection.
-			gcMode = AwlSimEnv.getGcMode()
-			wantManual = (gcMode == AwlSimEnv.GCMODE_MANUAL or
-				      (gcMode == AwlSimEnv.GCMODE_RT and
-				       self.__rtSchedEnabled))
-			if runstate == self.STATE_RUN and wantManual:
-				# Manual GC
-				gc.disable()
-				self.__gcManual = True
-				self.__gcTriggerCounter = 0
-				printVerbose("Switched to MANUAL garbage collection.")
+		try:
+			if runstate == self.STATE_RUN or\
+			   runstate == self.STATE_STOP:
+				# Reset instruction state dump.
+				self.__insnSerial = 0
+				for client in self.__clients:
+					for insnStateDump in dictValues(client.insnStateDump):
+						insnStateDump.msgs = []
+
+			if runstate == self._STATE_INIT:
+				# We just entered initialization state.
+				printVerbose("Putting CPU into INIT state.")
+				self.__setSched(allowRtPolicy=False)
+				self.__needOB10x = True
+			elif runstate == self.STATE_RUN:
+				# We just entered RUN state.
+				self.__startupTimeStamp = monotonic_time()
+				if self.__needOB10x:
+					printVerbose("CPU startup (OB 10x).")
+
+					# In case the hardware module spawns some threads make sure these
+					# inherit the affinity set and sched policy for peripherals.
+					self.__setAffinity(core=False)
+					self.__setSched(allowRtPolicy=True,
+							peripheral=True)
+					try:
+						# Start the hardware modules.
+						self.__sim.hardwareStartup()
+					finally:
+						# Go back to core affinity mask and sched policy.
+						self.__setAffinity(core=True)
+						self.__setSched(allowRtPolicy=True,
+								peripheral=False)
+
+					# Run the CPU statup and the CPU statup OBs.
+					self.__sim.startup()
+					self.__needOB10x = False
+				else:
+					# Set core sched policy.
+					self.__setSched(allowRtPolicy=True,
+							peripheral=False)
+				printVerbose("Putting CPU into RUN state.")
+			elif runstate == self.STATE_STOP:
+				# We just entered STOP state.
+				printVerbose("Putting CPU into STOP state.")
+				self.__setSched(allowRtPolicy=False)
+			elif runstate == self.STATE_MAINTENANCE:
+				# We just entered MAINTENANCE state.
+				self.__setSched(allowRtPolicy=False)
+				self.__needOB10x = True
+			else:
+				self.__setSched(allowRtPolicy=False)
+
+
+			# Select garbage collection mode.
+			if self.__gc_collect and self.__gc_get_count:
+				# If we are in RUN state with realtime scheduling,
+				# use manual garbage collection.
+				gcMode = AwlSimEnv.getGcMode()
+				wantManual = (gcMode == AwlSimEnv.GCMODE_MANUAL or
+					      (gcMode == AwlSimEnv.GCMODE_RT and
+					       self.__rtSchedEnabled))
+				if runstate == self.STATE_RUN and wantManual:
+					# Manual GC
+					gc.disable()
+					self.__gcManual = True
+					self.__gcTriggerCounter = 0
+					printVerbose("Switched to MANUAL garbage collection.")
+				else:
+					# Automatic GC
+					gc.enable()
+					self.__gcManual = False
+					printVerbose("Switched to AUTO garbage collection.")
 			else:
-				# Automatic GC
-				gc.enable()
+				# Manual GC control is not available.
 				self.__gcManual = False
 				printVerbose("Switched to AUTO garbage collection.")
-		else:
-			# Manual GC control is not available.
-			self.__gcManual = False
-			printVerbose("Switched to AUTO garbage collection.")
 
-		self.__state = runstate
-		# Make a shortcut variable for RUN
-		self.__running = bool(runstate == self.STATE_RUN)
+
+			self.__state = runstate
+			# Make a shortcut variable for RUN
+			self.__running = bool(runstate == self.STATE_RUN)
+
+		except Exception as e:
+			# An exception occurred. Go back to normal scheduling.
+			with suppressAllExc:
+				self.__setSched(allowRtPolicy=False)
+			raise e
 
 	def __getMnemonics(self):
 		return self.__sim.cpu.getConf().getMnemonics()
@@ -899,13 +942,13 @@ class AwlSimServer(object): #+cdef
 		return srcManager
 
 	def loadHardwareModule(self, hwmodDesc):
-		hwmodName = hwmodDesc.getModuleName()
-		printInfo("Loading hardware module '%s'..." % hwmodName)
-
 		# In case the hardware module spawns some threads make sure these
 		# inherit the affinity set for peripherals.
 		self.__setAffinity(core=False)
 		try:
+			hwmodName = hwmodDesc.getModuleName()
+			printInfo("Loading hardware module '%s'..." % hwmodName)
+
 			hwClass = self.__sim.loadHardwareModule(hwmodDesc.getModuleName())
 			self.__sim.registerHardwareClass(hwClass=hwClass,
 							 parameters=hwmodDesc.getParameters())
@@ -1280,39 +1323,43 @@ class AwlSimServer(object): #+cdef
 		reply.setReplyTo(msg)
 		client.transceiver.send(reply)
 
+	# Message receive control flags.
+	RXFLG_NONE	= 0		# No flags
+	RXFLG_EXFATAL	= 1 << 0	# AwlSimError exceptions are fatal
+
 	__msgRxHandlers = {
-		AwlSimMessage.MSG_ID_PING		: __rx_PING,
-		AwlSimMessage.MSG_ID_PONG		: __rx_PONG,
-		AwlSimMessage.MSG_ID_RESET		: __rx_RESET,
-		AwlSimMessage.MSG_ID_SHUTDOWN		: __rx_SHUTDOWN,
-		AwlSimMessage.MSG_ID_RUNSTATE		: __rx_RUNSTATE,
-		AwlSimMessage.MSG_ID_GET_RUNSTATE	: __rx_GET_RUNSTATE,
-		AwlSimMessage.MSG_ID_GET_AWLSRC		: __rx_GET_AWLSRC,
-		AwlSimMessage.MSG_ID_AWLSRC		: __rx_AWLSRC,
-		AwlSimMessage.MSG_ID_GET_SYMTABSRC	: __rx_GET_SYMTABSRC,
-		AwlSimMessage.MSG_ID_SYMTABSRC		: __rx_SYMTABSRC,
-		AwlSimMessage.MSG_ID_HWMOD		: __rx_HWMOD,
-		AwlSimMessage.MSG_ID_LIBSEL		: __rx_LIBSEL,
-		AwlSimMessage.MSG_ID_GET_FUPSRC		: __rx_GET_FUPSRC,
-		AwlSimMessage.MSG_ID_FUPSRC		: __rx_FUPSRC,
-#		AwlSimMessage.MSG_ID_GET_KOPSRC		: __rx_GET_KOPSRC,
-#		AwlSimMessage.MSG_ID_KOPSRC		: __rx_KOPSRC,
-		AwlSimMessage.MSG_ID_BUILD		: __rx_BUILD,
-		AwlSimMessage.MSG_ID_REMOVESRC		: __rx_REMOVESRC,
-		AwlSimMessage.MSG_ID_REMOVEBLK		: __rx_REMOVEBLK,
-#		AwlSimMessage.MSG_ID_GET_OPT		: __rx_GET_OPT,
-		AwlSimMessage.MSG_ID_OPT		: __rx_OPT,
-		AwlSimMessage.MSG_ID_GET_BLOCKINFO	: __rx_GET_BLOCKINFO,
-		AwlSimMessage.MSG_ID_GET_CPUSPECS	: __rx_GET_CPUSPECS,
-		AwlSimMessage.MSG_ID_CPUSPECS		: __rx_CPUSPECS,
-		AwlSimMessage.MSG_ID_GET_CPUCONF	: __rx_GET_CPUCONF,
-		AwlSimMessage.MSG_ID_CPUCONF		: __rx_CPUCONF,
-		AwlSimMessage.MSG_ID_REQ_MEMORY		: __rx_REQ_MEMORY,
-		AwlSimMessage.MSG_ID_MEMORY		: __rx_MEMORY,
-		AwlSimMessage.MSG_ID_INSNSTATE_CONFIG	: __rx_INSNSTATE_CONFIG,
-		AwlSimMessage.MSG_ID_GET_IDENTS		: __rx_GET_IDENTS,
-#		AwlSimMessage.MSG_ID_GET_CPUDUMP	: __rx_GET_CPUDUMP,
-		AwlSimMessage.MSG_ID_GET_CPUSTATS	: __rx_GET_CPUSTATS,
+		AwlSimMessage.MSG_ID_PING		: (__rx_PING,		RXFLG_NONE),
+		AwlSimMessage.MSG_ID_PONG		: (__rx_PONG,		RXFLG_NONE),
+		AwlSimMessage.MSG_ID_RESET		: (__rx_RESET,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_SHUTDOWN		: (__rx_SHUTDOWN,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_RUNSTATE		: (__rx_RUNSTATE,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_GET_RUNSTATE	: (__rx_GET_RUNSTATE,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_GET_AWLSRC		: (__rx_GET_AWLSRC,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_AWLSRC		: (__rx_AWLSRC,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_GET_SYMTABSRC	: (__rx_GET_SYMTABSRC,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_SYMTABSRC		: (__rx_SYMTABSRC,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_HWMOD		: (__rx_HWMOD,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_LIBSEL		: (__rx_LIBSEL,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_GET_FUPSRC		: (__rx_GET_FUPSRC,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_FUPSRC		: (__rx_FUPSRC,		RXFLG_EXFATAL),
+#		AwlSimMessage.MSG_ID_GET_KOPSRC		: (__rx_GET_KOPSRC,	RXFLG_NONE),
+#		AwlSimMessage.MSG_ID_KOPSRC		: (__rx_KOPSRC,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_BUILD		: (__rx_BUILD,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_REMOVESRC		: (__rx_REMOVESRC,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_REMOVEBLK		: (__rx_REMOVEBLK,	RXFLG_EXFATAL),
+#		AwlSimMessage.MSG_ID_GET_OPT		: (__rx_GET_OPT,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_OPT		: (__rx_OPT,		RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_GET_BLOCKINFO	: (__rx_GET_BLOCKINFO,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_GET_CPUSPECS	: (__rx_GET_CPUSPECS,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_CPUSPECS		: (__rx_CPUSPECS,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_GET_CPUCONF	: (__rx_GET_CPUCONF,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_CPUCONF		: (__rx_CPUCONF,	RXFLG_EXFATAL),
+		AwlSimMessage.MSG_ID_REQ_MEMORY		: (__rx_REQ_MEMORY,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_MEMORY		: (__rx_MEMORY,		RXFLG_NONE),
+		AwlSimMessage.MSG_ID_INSNSTATE_CONFIG	: (__rx_INSNSTATE_CONFIG, RXFLG_NONE),
+		AwlSimMessage.MSG_ID_GET_IDENTS		: (__rx_GET_IDENTS,	RXFLG_NONE),
+#		AwlSimMessage.MSG_ID_GET_CPUDUMP	: (__rx_GET_CPUDUMP,	RXFLG_NONE),
+		AwlSimMessage.MSG_ID_GET_CPUSTATS	: (__rx_GET_CPUSTATS,	RXFLG_NONE),
 	}
 
 	def __clientCommTransferError(self, exception, client):
@@ -1327,6 +1374,7 @@ class AwlSimServer(object): #+cdef
 		self.__clientRemove(client)
 
 	def __handleClientComm(self, client): #+cdef
+		flags = self.RXFLG_EXFATAL
 		try:
 			msg = client.transceiver.receive(0.0)
 			if not msg:
@@ -1335,13 +1383,13 @@ class AwlSimServer(object): #+cdef
 				printInfo("Received unsupported "
 					  "message 0x%02X" % msg.msgId)
 				return
-			handler = self.__msgRxHandlers[msg.msgId]
+			handler, flags = self.__msgRxHandlers[msg.msgId]
 			handler(self, client, msg)
 		except AwlSimError as e:
-			# Communication error. Just log it.
-			# We don't forward this error to the main loop.
-			printError(e.getReport())
-			return
+			if not (flags & self.RXFLG_EXFATAL):
+				# Just report this nonfatal exception.
+				e.setReportOnlyFlag(True)
+			raise e
 		except TransferError as e:
 			self.__clientCommTransferError(e, client)
 			return
@@ -1613,17 +1661,21 @@ class AwlSimServer(object): #+cdef
 				printVerbose("Main loop exception: %s" % (
 					     e.getMessage()))
 
-				# Stop the CPU
-				self.setRunState(self.STATE_STOP)
-				# Schedule a CPU restart/rebuild.
-				self.__needOB10x = True
+				if not e.getReportOnlyFlag():
+					# Stop the CPU
+					self.setRunState(self.STATE_STOP)
+					# Schedule a CPU restart/rebuild.
+					self.__needOB10x = True
 
 				# Try to add more information to the exception.
 				self.__extendAwlSimError(e)
 
 				if self.__handleExceptionServerside:
 					# Let the server handle the exception
-					raise e
+					if e.getReportOnlyFlag():
+						printError(e.getReport())
+					else:
+						raise e
 				else:
 					# Send the exception to all clients.
 					msg = AwlSimMessage_EXCEPTION(e)