Tag.configure max recursion depth exceeded

Hi,

I work on a script which add an alarm in tags.
I use system.tag.getConfiguration() to get json tag and I add an alarm in ‘alarms’ key.
My config before adding new alarm :

{
  "tagGroup": "PHPScriptExecution",
  "expression": "runScript(\"shared.alarms.daily(\"+{device_id}+\",\"+{project_id}+\",\"+{dailyHour1}+\",\"+{dailyHour2}+\")\")",
  "alarms": [],
  "query": "SELECT avg(value) FROM admin.m_data WHERE device_id\u003d{device_id} and project_id\u003d{[~]project_id} and datetime \u003e\u003d DATE_FORMAT(now()-interval 1 day,\u0027%Y-%m-%d 00:00:00\u0027) and datetime \u003c DATE_FORMAT(now()-interval 1 day,\u0027%Y-%m-%d 23:59:59\u0027)",
  "dataType": "Float4",
  "writePermissions": "{\"type\":\"AllOf\",\"securityLevels\":[]}",
  "executionMode": "TagGroupRate",
  "readOnly": false,
  "enabled": true,
  "path": "[test]ACW-TH 1CD279/value_daily",
  "readPermissions": "{\"type\":\"AllOf\",\"securityLevels\":[]}",
  "datasource": "",
  "tagType": "AtomicTag",
  "name": "value_daily",
  "valueSource": "expr"
}

My config after :

{
  "tagGroup": "PHPScriptExecution",
  "expression": "runScript(\"shared.alarms.daily(\"+{device_id}+\",\"+{project_id}+\",\"+{dailyHour1}+\",\"+{dailyHour2}+\")\")",
  "alarms": [
    {
      "activePipeline": "alarm-pipelines/home_pipeline",
      "setpointA": 18,
      "CustomEmailSubject": {
        "bindType": "Expression",
        "value": "concat(\"[ALARME] \",if(isNull(jsonGet({[~]project_info.value},\u0027outputs\u0027)),\u0027#titleError\u0027,jsonGet({[~]project_info.value},\u0027outputs[0].title\u0027)),\" : \",{name})"
      },
      "setpointB": 25,
      "displayPath": {
        "bindType": "Expression",
        "value": "concat(\u0027user/\u0027,{database},\u0027/\u0027,toStr(toInt({alarm_id})),\u0027/\u0027,{name})"
      },
      "priority": "High",
      "enabled": true,
      "mode": "OutsideValues",
      "name": "confort test",
      "alarm_id": 162,
      "inclusiveA": false,
      "CustomEmailMessage": {
        "bindType": "Expression",
        "value": "concat(\"Valeur : \\\"\",{setpointA},\"\\\" | Type : Est \\xe9gale xe0  \",\" | \",\"Valeur en alerte : moyenne sur \\\"\",{nb_hours},\"\\\" heures : \\\"\",{value})\")"
      },
      "inclusiveB": false
    }
  ],
  "query": "SELECT avg(value) FROM admin.m_data WHERE device_id\u003d{device_id} and project_id\u003d{[~]project_id} and datetime \u003e\u003d DATE_FORMAT(now()-interval 1 day,\u0027%Y-%m-%d 00:00:00\u0027) and datetime \u003c DATE_FORMAT(now()-interval 1 day,\u0027%Y-%m-%d 23:59:59\u0027)",
  "dataType": "Float4",
  "writePermissions": "{\"type\":\"AllOf\",\"securityLevels\":[]}",
  "executionMode": "TagGroupRate",
  "readOnly": false,
  "enabled": true,
  "path": "[test]ACW-TH 1CD279/value_daily",
  "readPermissions": "{\"type\":\"AllOf\",\"securityLevels\":[]}",
  "datasource": "",
  "tagType": "AtomicTag",
  "name": "value_daily",
  "valueSource": "expr"
}

I have error below :

Blockquote
com.inductiveautomation.ignition.common.script.JythonExecException
Traceback (most recent call last):
File “function:runAction”, line 5, in runAction
File “module:alarm”, line 1364, in createAlarm
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)
at org.python.core.Py.RuntimeError(Py.java:157)
at org.python.core.Py.JavaError(Py.java:540)
at org.python.core.Py.JavaError(Py.java:538)
at org.python.core.PyReflectedFunction.call(PyReflectedFunction.java:192)
at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.call(ScriptManager.java:546)
at org.python.core.PyObject.call(PyObject.java:494)
at org.python.core.PyObject.call(PyObject.java:498)
at org.python.pycode._pyx9885.createAlarm$20(module:alarm:1388)
at org.python.pycode._pyx9885.call_function(module:alarm)
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:134)
at org.python.core.PyFunction.call(PyFunction.java:416)
at org.python.pycode.pyx10296.runAction$1(function:runAction:12)
at org.python.pycode.pyx10296.call_function(function:runAction)
at org.python.core.PyTableCode.call(PyTableCode.java:173)
at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
at org.python.core.PyFunction.function___call
(PyFunction.java:474)
at org.python.core.PyFunction.call(PyFunction.java:469)
at org.python.core.PyFunction.call(PyFunction.java:464)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:849)
at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:831)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:689)
at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:1000)
at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:754)
at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:136)
at com.inductiveautomation.perspective.gateway.action.ScriptAction.runAction(ScriptAction.java:71)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.lambda$call$0(ActionCollection.java:263)
at com.inductiveautomation.perspective.gateway.api.LoggingContext.mdc(LoggingContext.java:54)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:252)
at com.inductiveautomation.perspective.gateway.model.ActionCollection$ActionSequence$ExecuteActionsTask.call(ActionCollection.java:221)
at com.inductiveautomation.perspective.gateway.threading.BlockingTaskQueue$TaskWrapper.run(BlockingTaskQueue.java:154)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:42)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: org.python.core.PyException
Traceback (most recent call last):
File “function:runAction”, line 5, in runAction
File “module:alarm”, line 1364, in createAlarm
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)

Thanks for your help. :smiling_face_with_three_hearts:

Script row concerned :

tagAlarmsCnf = tagCnf['alarms']
tagAlarmsCnf.append(inputsConfig)
tagCnf['alarms'] = tagAlarmsCnf
	
collisionPolicy = "o"
tagRes = system.tag.configure(baseTagPath, [tagCnf], collisionPolicy)

Hi,
I don’t know how you have made the whole script but the very last row in the error code tells me that you are making too many recursive calls to that same function.

By googling that Runtime Error I get the following message:

The “maximum recursion depth exceeded in comparison” error is raised when you try to execute a function that exceeds Python’s built in recursion limit. You can fix this error by rewriting your program to use an iterative approach or by increasing the recursion limit in Python

Are you adding the same alarm to all of your tags at once? Try limiting the code to just add the alarm to a single tag and see if that solves the issue.
You will probably need to limit how many times you call the recursive function.

I configure only one tag. :face_with_raised_eyebrow:
May be because it’s a UDT instance.

My entire script :

def createAlarm(inputs):
		
	import features
	try:
		inputs=dict(inputs)
	except:
		return {'state':False,'message':u"La variable d'entrée n'est pas un dictionnaire.","outputs":[], "meta":features.msgMeta(False)}
		
	#On récupère les infos de l'alarme
	inputsKeys = inputs.keys()
	#Le tagPath
	if "tagPath" in inputsKeys:
		tagPath = inputs['tagPath']
	else:
		return {'state':False,'message':u"La variable tagPath n'est pas indiquée.","outputs":[], "meta":features.msgMeta(False)}
	
	#Le database
	if "database" in inputsKeys:
		database = inputs['database']
	else:
		return {'state':False,'message':u"La variable database n'est pas indiquée.","outputs":[], "meta":features.msgMeta(False)}
	
	#Le database
	if "tagName" in inputsKeys:
		tagName = inputs['tagName']
	else:
		return {'state':False,'message':u"La variable database n'est pas indiquée.","outputs":[], "meta":features.msgMeta(False)}
		
	#On compose le nom complet du tag
	fullTagPath = "["+database+"]"+tagPath+"/"+tagName
	baseTagPath = "["+database+"]"+tagPath
	
	#ON récupère la config du tag
	tags = system.tag.getConfiguration(fullTagPath)
	
	if len(tags) == 0:
		return {'state':False,'message':u"Aucun tag n'a été trouvé pour le capteur indiqué. Si le problème persiste, contacter l'administrateur.","outputs":[], "meta":features.msgMeta(False)}
	else:
		tagCnf = tags[0]
		if 'tagType' in tagCnf.keys():
			tagType = tagCnf['tagType']
			
			if tagType == "Unknown":# Cas où le tag n'existe pas
				return {'state':False,'message':u"Le tag de ce capteur n'existe pas. Nom : "+tagCnf['path']+". Si le problème persiste, contacter l'administrateur.","outputs":[], "meta":features.msgMeta(False)}	
					
	
	alarmCols = ['alarm_name','description','alarm_type','alarm_mode','alarm_agg','device_id','project_id','limit1','limit2','unit','recipients','mode','state','email_trigger','isPreset']
	queryCols = []
	queryVars = []
	query = "INSERT INTO alarms_config ("
	questionMarks = ""
	
	for alarmCol in alarmCols:
		if alarmCol in inputsKeys:
			queryCols.append(alarmCol)
			query += " %s,"
			questionMarks += " ?,"
			queryVars.append(inputs[alarmCol])
	
	txId = system.db.beginTransaction(database="base_administrateur",timeout=5000)
	
	query = query[:-1] % tuple(queryCols) + ") VALUES ("+questionMarks[:-1]+")"
	alarm_id = system.db.runPrepUpdate(query,queryVars,"base_administrateur",tx=txId, getKey=1)
	try:
		alarm_id = system.db.runPrepUpdate(query,queryVars,"base_administrateur",tx=txId, getKey=1)
	except:
		errors = {'state':False,'message':u"Une erreur s'est produite lors de l'ajout de l'alarme dans la base de données.","outputs":[], "meta":features.msgMeta(False)}
	else:
		errors = {'state':True,'message':u"L'alarme a bien été créée.","outputs":[], "meta":features.msgMeta(True)}
		
	#Gestion de l'erreur
	if errors['state']==False:
		# ...then rollback the transaction
		system.db.rollbackTransaction(txId)
	else:
		# ON met l'alarm_id dans la configuration du tag
		inputsConfig = inputs['config']
		inputsConfig['alarm_id'] = alarm_id
		
		#On ajoute sa configuration dans la variable des alarmes
		tagAlarmsCnf = tagCnf['alarms']
		tagAlarmsCnf.append(inputsConfig)
		tagCnf['alarms'] = tagAlarmsCnf
		
		collisionPolicy = "o"
		tagRes = system.tag.configure(baseTagPath, [tagCnf], collisionPolicy)
	
			
		if tagRes[0].isGood():
			# Si le tag a été mis à jour on ajoute sa configuration dans la table alrms_config
			try:
				system.db.runPrepUpdate("UPDATE alarms_config SET config = ? WHERE alarm_id = ?",[features.encodeJSON(inputs['config']),alarm_id],"base_administrateur",tx=txId)
			except:
				errors = {'state':False,'message':u"Une erreur s'est produite lors de la mise à jour de l'alarme dans la base de données.","outputs":[], "meta":features.msgMeta(False)}
			else:
				errors = {'state':True,'message':u"L'alarme a bien été créée.","outputs":[], "meta":features.msgMeta(True)}
				
			if errors['state']:
				# Otherwise, commit it
				system.db.commitTransaction(txId)
			else:
				system.db.rollbackTransaction(txId)
				
		else:
			system.db.rollbackTransaction(txId)
			errors = {'state':False,'message':u"Une erreur s'est produite lors de la mise à jour du tag de l'alarme.","outputs":[], "meta":features.msgMeta(False)}
	
	system.db.closeTransaction(txId)
	
	return errors 

How are you calling this function ?

Unrelated : may I suggest you split this up in more specialized functions ? This kind of script is a pain to maintain/debug, especially if you’re picking up the job after someone else.
I’ve been re-writing scripts like this, left there by someone else, for the last few months because they’ve come to a point where it’s easier to just rewrite than fix.
This one is still readable, but after a couple fixes/reworks…

I call it in a onActionPerformed button.

Script is stored in Project Library.

It’s a little hard to tell but we have a similar issue that was written up a little while back where encoding the config from system.tag.getConfiguration() to json can throw this error due to an internal issue with Ignition.
If the error is occurring on:

features.encodeJSON(inputs['config'])

it may work if you change it to

features.encodeJSON(str(inputs['config']))

Can I use : sys.setrecursionlimit(5000) ?

No. It’s a bug in the Java code, and the sys flag only affects Jython’s execution environment.
Also, the ‘fix’ for a StackOverflowError is pretty much never “make the stack larger”. It’s a programming error (on our part, not yours) to fix.

2 Likes

I optimized my script and it works:

Below part of script updated.
Before →

#Gestion de l'erreur
if errors['state']==False:
	# ...then rollback the transaction
	system.db.rollbackTransaction(txId)
else:
	# ON met l'alarm_id dans la configuration du tag
	inputsConfig = inputs['config']
	inputsConfig['alarm_id'] = alarm_id
	
	#On ajoute sa configuration dans la variable des alarmes
	tagAlarmsCnf = tagCnf['alarms']
	tagAlarmsCnf.append(inputsConfig)
	tagCnf['alarms'] = tagAlarmsCnf
	
	collisionPolicy = "o"
	tagRes = system.tag.configure(baseTagPath, [tagCnf], collisionPolicy)

After →

#Gestion de l'erreur
if errors['state']==False:
	# ...then rollback the transaction
	system.db.rollbackTransaction(txId)
else:
	# ON met l'alarm_id dans la configuration du tag
	inputs['config']['alarm_id'] = alarm_id
			
	#On ajoute sa configuration dans la variable des alarmes
	if "alarms" in tags[0].keys():
		tags[0]['alarms'].append(inputs['config'])
	else:
		tags[0]['alarms'] = [inputs['config']]
						
	tagCnfForConfigure = [
	            {
	                "alarms":tags[0]['alarms'],
	                "name":tagName
	            }
	        ]
	
	collisionPolicy = "o"
	tagRes = system.tag.configure(baseTagPath, tagCnfForConfigure, collisionPolicy)