2

So I was messing around with a script that is supposed to restart itself using os.execl. It is working a few times but after ~ 30 or 40 calls it crashes;

Traceback (most recent call last):
  File "C:\Users\Admin#\PycharmProjects\DiscordBot_Fred_the_Financier\test_suit.py", line 9, in <module>
    os.execl(sys.executable, sys.executable, *(*sys.argv, code))
  File "C:\Users\Admin#\AppData\Local\Programs\Python\Python37\lib\os.py", line 540, in execl
    execv(file, args)
OSError: [Errno 12] Not enough space

So this is the whole code I'm running actually:

import sys 
import os

print(sys.argv) # print args
code = "" # placeholder for mutable args
os.execl(sys.executable, sys.executable, *(*sys.argv, code)) # passing new args and replacing process

I have literally no idea why and how this error occurs.
All my drives have >200 GB free storage and my RAM more than 17 GB as well.
I'm running this code via terminal on Win10 64bit python 3.7.
Thank you very much for your help!

P.S. I apologize if there is already an answer to this problem but I could not find one.

Core taxxe
  • 299
  • 2
  • 12
  • 2
    Did you ever find a solution to this? I'm experiencing a similar problem writing pickle files. I'm only using 20% of RAM on server and this error message pops up even though nothing appears to be maxing out on the computer. **Update** https://yantor3d.wordpress.com/2019/06/09/not-enough-memory-mine-more-memory/ It appears the problem is in os.fork(). Found a person who located the source of the problem but was not able to share their solution due to NDA. – KirkLab Feb 10 '21 at 17:35
  • @KirkLab no i haven't been able to fix that issue yet :( – Core taxxe Feb 15 '21 at 08:33
  • Environment variables and command line arguments share the same pool of space. You get this problem if you `export` too much data. – Charles Duffy Feb 12 '23 at 13:48

2 Answers2

0

Are you opening any huge files in your script? Most likely you are not closing those file handles and they keep accumulating. Once the script crashes all the handles will be released and you see the 200GB

While running the script, can you keep an eye on disk usage? Do you see it rising continuously? (at least after the subsequent calls of exec)

EDIT: I see now that the question was asked in 'Mar 28 '20 at 18:58'. I do not know why I saw it in main list. Question can be closed if OP does not reply or gives more info.

lllrnr101
  • 2,288
  • 2
  • 4
  • 15
  • Hello! So I kept an eye on my resources and no my ssd is on 1% usage the whole time. And btw this is the whole script that is needed to replicate the issue. (Neither my cpu nor ram are dying as well) – Core taxxe Feb 15 '21 at 08:20
0

I haven't found why this error happens, but I am assuming it has something to do with the parent process not being able to fully close (maybe due to references left or something similar). I actually found a workaround which I am leaving here in case it helps you or someone else with the same problem.

The workaround is using subprocess.Popen instead of os.execl, and immediately adding os._exit(1). What the latter does is immediately shut down the parent process and thus free all resources. So if you do this, you won't get the 'Not enough space' error, no matter how many times you restart the process. To verify this, I made a test, by running these two lines of code in an infinite loop (although in retrospective, it's not necessary as a second iteration is impossible due to os._exit(1) statement), and leaving it running for a while, and writing to a .txt file per each process replacement. After some considerable amount of time, I stopped the final process by interrupting it with Ctrl+C, and when I opened the file, I found the value 15127, which means that the process had been replaced 15127 times before I interrupted it and I never had any exceptions raised due to space issues.

Additionally, as with os.execl, you can pass command-line arguments by passing sys.argv, for example by typing:

import os
import sys
import subprocess

print(sys.argv)
code = ""
subprocess.Popen([sys.executable, *(*sys.argv, code)])
os._exit(1)

I omitted the second sys.executable because I am not sure of what you are trying to do and if it would work in subprocess.Popen, but given the similarity of these two commands, I'd say it would work the same way if you typed subprocess.Popen(sys.executable, sys.executable, *(*sys.argv, code) instead.

EDIT: Actually, with subprocess.Popen, you need to pass all arguments as a list, as otherwise the second argument you pass will be considered a bufsize according to the method signature (see https://docs.python.org/3/library/subprocess.html#popen-constructor for further details), or as a string concatenation, if possible (for example, subprocess.Popen(sys.executable + " " + code). Also, I added a missing parenthesis.

Luciano P.
  • 39
  • 3
  • The limit being hit is the combined command line arguments and environment variable space. It's not at all clear to me that the change proposed in this answer would help very much – Charles Duffy Feb 12 '23 at 13:49
  • Are you saying that the root cause of the problem is memory management? If yes, I agree with you. On the other hand, the change proposed did help me and that's why I am sharing it with others. – Luciano P. Feb 12 '23 at 13:57
  • Not _general_ memory management, but management _specifically_ of the chunk of kernel memory used to pass environment variables and argv elements between processes. So the easy way to avoid this problem altogether is not to `export` things that don't _really need_ to be in the environment (so: use regular shell variables, not environment variables, wherever possible); or to, say, pass file _names_ through the environment as opposed to those files' contents, if you have something that's really big that needs to be passed around. – Charles Duffy Feb 12 '23 at 15:45
  • Alright, I understand your point now, thank you for the clarification, Charles. However, I think the `OSError: `[Errno 12] Not enough space` is not related to the size of contents being passed, since I was running `os.execl(sys.executable, sys.executable, *sys.argv)`inside a file with just a print statement and the necessary imports for the test, with no env or arg variables and it would always stop at 114 (I knew because I used a 1-line external file as a counter for each time os.execl was run). With subprocess.Popen, I got the same results as OP (script restarted itself) and without errors. – Luciano P. Feb 12 '23 at 16:23
  • Note that it's not just the _number_ of items on your argv but also the _size_ of those items. Granted, they're NUL-terminated, so you need to add 1 to the length of each one. Same for environment variables; each one uses its actual length plus an extra byte for the terminating NUL. – Charles Duffy Feb 12 '23 at 19:21