AviSynth / AviSynthPlus

AviSynth with improvements
http://avs-plus.net
963 stars 73 forks source link

[Reproducible] PropSet inside ScriptClip crashes Avisynth #359

Open flossy83 opened 1 year ago

flossy83 commented 1 year ago

This is crashing after 2600-2800 frames rendered:

global bars = Colorbars().Killaudio().ConvertToYV12() #.ChangeFPS(200) # speed up the bug

ScriptClip(bars, "CrashMe(bars, current_frame)", after_frame=true, local=false)

function CrashMe(clip c, int curr_frame){

    # Crashes
    global bars = bars.propSet("curr_frame", curr_frame)
    bars

    # Doesn't crash
    # out = bars.propSet("curr_frame", curr_frame)
    # out   
}

PropShow()

This is reproducible on both my systems with 3.7.2 & 3.7.3 under the following scenarios:

In all cases the rendering of the script just stops abruptly at around the 2600-2800 frame mark with no error message (I tried try & catch block to no effect). On one of my machines MPC-HC was reporting ntdll.dll as the crashing module, on the other MPC-HC just closes without any error. ffmpeg.exe just stops rendering without any error.

Speculation: perhaps the ScriptClip and Avisynth are both trying to write frame properties at the same time?

flossy83 commented 1 year ago

Speculation: perhaps the ScriptClip and Avisynth are both trying to write frame properties at the same time?

Seems not to be the case as reading & writing frame props to a separate clip (bars2) also triggers it:

global bars = Colorbars().Killaudio().ConvertToYV12().ChangeFPS(200) #speed up bug
global bars2 = Colorbars().Killaudio().ConvertToYV12().ChangeFPS(200) #speed up bug

ScriptClip(bars, "CrashMe(bars, current_frame)", after_frame=true, local=false)

function CrashMe(clip c, int curr_frame){

    # Scenario 1 - crashes. Implies problem is caused by writing props to the clip being iterated on
    # global bars = bars.propSet("curr_frame", curr_frame)
    # bars

    # Scenario 2 - doesn't crash, consistent with scenario 1's implication
    # global bars2 = bars2.propSet("curr_frame", curr_frame)    
    # bars

    # Scenario 3 - crashes despite using different clip, thus contradicting scenario 1&2's hypothesis
    if (curr_frame != 0){ var = propGetAny(bars2, "curr_frame") }
    global bars2 = bars2.propSet("curr_frame", curr_frame)  
    bars

    # Scenario 4 - doesn't crash, but can't use frame props for messaging between frames
    # out = bars.propSet("curr_frame", curr_frame)
    # out

}
PropShow()
pinterf commented 1 year ago

Thanks, I can see the crash, thanks for the test script. EDIT: debug build is showing "Stack overflow", which may occur when too many nested functions are involved (or for example current frame is recursively calling all previous frames backwards, one by one, for some reason) but at the first sight this is not the case. Mine is crashing at about frame number 673, and avsmeter is showing decreasing speed until the crash as if more and more frames would be needed to fetch for the specific current frame.... Investigating further. EDIT:typo

pinterf commented 1 year ago

There is something with setting "global" clip within the function and/or after_frame=true.

It seems that for the first frame the function is doing this: global bars = bars.propSet("curr_frame", curr_frame) for frame number = 2 global bars = bars.propSet("curr_frame", curr_frame).propSet("curr_frame", curr_frame) for frame number = 5 global bars = bars.propSet("curr_frame", curr_frame).propSet("curr_frame", curr_frame).propSet("curr_frame", curr_frame).propSet("curr_frame", curr_frame).propSet("curr_frame", curr_frame) for frame number = 700 (... imagine)

PropSet gets its child clip, which has another child clip, which has another child clip...

When a big enough depth (~700+ in my debug build) is reached, the initial calling stack (1 MB) is consumed totally ,it will crash with stack overflow (debug build reports this fatal error)

This is why avsmeter was showing incremental slowdown, as more and more call was done as the frame count advanced.

For whatever is the reason, I don't understand it yet. I don't even know that this is a legal use case, or illegal but Avisynth's script parser cannot detect it and is not able to show script error beforehand.

flossy83 commented 1 year ago

Ok well thanks for looking into it, I guess it's just a limitation.

The reason I wanted to write frameprop to global clip is so I can check that same prop the next frame and see what the counter was, for purpose of detecting multithreading desynchronisation and warning user.

Storing the counter in a global var instead of frameprop seems to work too, but I thought it would be safer using a frameprop since Avisynth always displays frames in chronological order, whereas ScriptClip may not occur in chronological order if mulithreading. So the frameprop counter is like a guaranteed reference point for knowing what the counter should be for a given frame.