4

I actually joined StackOverflow to ask this question because I wasn't sure it was RPi specific, but as I was thinking about how to form the question, I realized it might be GPIO specific (for some reason that I'm not even aware of), so hopefully this is the right place to ask.

I'm running Raspbian Jessie 4.9.35-v7+ on a Pi 3 Model B. Over time, I've cobbled together a fairly basic python script to monitor a GPIO pin and send a network signal to a remote application. I run two slightly different copies of the script to monitor two different GPIO pins for two different network signals, and the scripts are launched from rc.local. A previous version of these scripts ran for months on end, but I just had the current version fail after maybe two weeks due to "RuntimeError: maximum recursion depth exceeded." The error output logged includes bouncing back and forth between two functions many times, so I ran this against the log:

cat script2.log | grep -c File

The log for script1 is 0 bytes, so I believe that command gives me a number that shows roughly how many levels of recursion the script encountered. The output was 984, and paired with the two functions I wrote repeating back and fourth all but 7 of those times, the 977 iterations seem high enough to indicate the problem lies in how my script is written as opposed to within an imported package. Here is an example of the script I am running:

#!/usr/bin/env python
import RPi.GPIO as GPIO
import signal
import socket
import time
import requests
#Define GPIO pin here:
thiscontact = 2
GPIO.setmode(GPIO.BCM)
GPIO.setup(thiscontact, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def watchopen():
    thisposition = GPIO.input(thiscontact)
    if thisposition == True:
        while thisposition == True:
            time.sleep(0.1)
            thisposition = GPIO.input(thiscontact)
        watchclose()
    watchclose()
def watchclose():
    thisposition = GPIO.input(thiscontact)
    if thisposition == False:
        while thisposition == False:
            #Send Trigger
            requests.get('https://192.0.2.248:55756/Srv/Api/TriggerFacade/ActivateDeactivateTrigger?{"triggerName":"SignalOne","deactivateAfterSeconds":"3"}', auth=('Username', 'Password'), verify=False, timeout=1)
            time.sleep(0.9)
            thisposition = GPIO.input(thiscontact)
        watchopen()
    watchopen()
def exit_handler(signal, frame):
    GPIO.cleanup()
    raise SystemExit
signal.signal(signal.SIGTERM, exit_handler)
signal.signal(signal.SIGINT, exit_handler)
watchopen()

This is what the end of the log file would look like from that example:

  File "/usr/local/bin/Door6B.py", line 34, in watchclose
    watchopen()
  File "/usr/local/bin/Door6B.py", line 20, in watchopen
    watchclose()
  File "/usr/local/bin/Door6B.py", line 28, in watchclose
    requests.get('https://192.0.2.248:55756/Srv/Api/TriggerFacade/ActivateDeactivateTrigger?{"triggerName":"SignalOne","deactivateAfterSeconds":"3"}', auth=('Username', 'Password'), verify=False, timeout=1)
  File "/usr/lib/python2.7/dist-packages/requests/api.py", line 60, in get
    return request('get', url, **kwargs)
  File "/usr/lib/python2.7/dist-packages/requests/api.py", line 48, in request
    session = sessions.Session()
  File "/usr/lib/python2.7/dist-packages/requests/sessions.py", line 327, in __init__
    self.mount('https://', HTTPAdapter())
  File "/usr/lib/python2.7/dist-packages/requests/adapters.py", line 90, in __init__
    self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
  File "/usr/lib/python2.7/dist-packages/requests/adapters.py", line 126, in init_poolmanager
    block=block, **pool_kwargs)
  File "/usr/lib/python2.7/dist-packages/urllib3/poolmanager.py", line 65, in __init__
    dispose_func=lambda p: p.close())
  File "/usr/lib/python2.7/dist-packages/urllib3/_collections.py", line 46, in __init__
    self._container = self.ContainerCls()
RuntimeError: maximum recursion depth exceeded

Above that in the log file, the first two sections repeat 974 times following the first bit which is roughly the same:

Traceback (most recent call last):
  File "/usr/local/bin/Door6B.py", line 40, in <module>
    watchopen()

With a bit of searching, I've found advice such as "Rewriting the algorithm iteratively, if possible, is generally a better idea" and "For instance, it's quite possible that your outermost loop will hinge on a simple while." However, I believe I previously found that I had to loop in order for the script to continue running indefinitely when I tested last year, so I am guessing that those answers don't apply to me and the assumption with those answers is that the script needs to complete a task and end.

I'm not sure how to proceed for a number of reasons such as these:

1) The original version of this script that ran for months on end used socket instead of requests (and it looks like I never stopped importing that, so some script cleanup may be in order). Given this, while it seems unlikely to me, it may be possible that I'm not understanding recursion right and there is an issue in requests that caused this error vs the bouncing back and forth between my functions.

2) I have no idea how to make a script perform an action only when a switch is closed each time a switch is closed without this recursion, and I have no idea what to search for in order to find out how if that can even be done.

3) Even assuming the second piece of advice I found applies, if I remember correctly, I added the if loops around the while loops in order to prevent the script from terminating. However, logically speaking, even if the if loops are redundant and it would work with only while loops, I'm not sure that such a change would remove the recursion since each function still calls the other.

4) I suspect that I could have a bash script call the python script in a loop by only having the contents of watchclose() at the end of the python script followed by time.sleep(0.1) (or some sleep function in the bash loop), but I doubt that is the "right" way to deal with this from a programming standpoint or an efficiency standpoint.

  • 1
    I posted this and then immediately came up with a search idea. stackoverflow.com/questions/438844/… might provide several legitimate options. I'm not even sure if I need to use an exception or if I can just place both of my functions (or even just the code from the functions) inside of a while loop that will always remain active so they don't need to call each other. – rpseu Jul 1 at 13:30
  • Frankly you would be better to use gpiozero which includes Button functions to react to press, release etc includes debouching and avoids wasteful loops. This is included in current Raspbian. – Milliways Jul 1 at 22:29
  • @rpseu, I have the feeling that you don't need two functions calling each other. Can you let us know in a couple of sentences what you wish to do, for example: "OK, I wish to continuously monitor if a switch is off or on. If switch is on, Rpi GPIO reads High, and Low if off. Rpi uses socket or request to let the guy over the other side of the network know what is going on. – tlfong01 Jul 2 at 3:51
  • Let me guess why you have the error: "RuntimeError: maximum recursion depth exceeded.". I think this means the "stack overflows". In other words, it is NOT other resources (for opening socket, say) used up. I guess the cause for stack overflow is as the error message says: "recursion runs too deep", in your case over 900+ calls deep, and the stack overflows, ie, no more storage spaces left in the stack to store the newly called process information (address pointer of where it comes from, so to know where to go back to ), arguments by reference or values etc, /to continue, ... – tlfong01 Jul 2 at 4:55
  • Now let me divert to the idea of recursion, to show you are NOT doing recursion, but only 2 functions calling each other, making a mess. Real, mathematically beautiful recursive algorithm is to solve a problem. An algorithm is recursive IF AND ONLY IF it has the following features: (1) Breaking down a big problem into smaller problem, again and again, until finally the smallest problem cannot be further broken down, or immediately solved. EG, break down Factorial 5 into two smaller problems, (a) Factorial 4, (b) a multiplication. Final break down reaches Fact 0 = 1 by definition.... – tlfong01 Jul 2 at 5:09
4

When you have a recursive function like this (one with too much depth potential), you need to convert it into a loop. In this case you have two functions alternating calling each other:

while True:
   watchopen()
   watchclose()

This will loop forever (as does the original version, you are exiting on a signal), but you can also use some kind of state as a condition in the while loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.