obsproject / obs-studio

OBS Studio - Free and open source software for live streaming and screen recording
https://obsproject.com
GNU General Public License v2.0
57.29k stars 7.72k forks source link

Text blanks, fails to read from file #3011

Open Meerkov opened 4 years ago

Meerkov commented 4 years ago

Platform

Windows 10 OBS Studio version: 25.0.1 64 bit

Expected Behavior

1) Create a Text Source with "Read File" enabled. 2) Switch to a scene where the Text Source is deleted 3) Update the text file manually in another program (or use a script in a separate process). 4) Switch back to the first scene 5) Repeat step 2-4 another 10x

Result: The text updates every time

Current Behavior

Sometimes the TextSource will become blank even though the file is not empty. The TextSource may sometimes recover if content is written to the file again, but sometimes will not.

Steps to Reproduce

1) run this in OBS. Make sure to have a text source with "Read File" on one scene, but deleted on the other.

obs = obslua

function change_scene(sceneName)
    source = obs.obs_get_source_by_name(sceneName)
    if source ~= nil then
        obs.obs_frontend_set_current_scene(source)
        obs.obs_source_release(source)
    else
        print("Can't find scene: "..sceneName)
    end
end

function display_timer_tick()
    if(state == "DisplayText") then
                state == "HideTextDuringUpdate"
        change_scene("s1")
        else
                state == "DisplayText"
                change_scene("s2")
        end
end

function script_load(settings)
    print("Loaded Script.")
        obs.timer_add(display_timer_tick, 10000)
end

2) Update the textfile while the text is hidden. 3) Watch to see if the text appears as expected. 4) if it appears, edit the textfile and save again when it disappears. (I used a separate script to synchronize the file-write with OBS's scene switching, but you should be able to do this manually instead). 5) Repeat until it shows up blank. 6) Stop editing the file and wait to see it probably doesn't read the file until you edit it again. If it doesn't work, try "refreshing" the lua script in OBS.

Additional information

It might be something related to the source getting disabled during scene switching, which the documentation acknowledges "is confusing to users because it's not surfaced in the UI and sources will fail to render even though they appear to be marked visible". But I would have no idea what might cause that to happen here.

notr1ch commented 4 years ago

Are you performing atomic updates of the text file? If not, OBS may be reading the file as soon as it's open for writing but before the contents are written.

YorVeX commented 3 years ago

Are you performing atomic updates of the text file? If not, OBS may be reading the file as soon as it's open for writing but before the contents are written.

At least for #3275 I am pretty sure that's what happens. IMHO in these cases OBS should retry to read the file later. Otherwise it means that you can create situations where the OBS source does not reflect the contents of the file anymore, but that is the whole idea of that mechanism and I want to be able to trust it. For performance reasons OBS should maybe not try too aggressively to re-access the file (it might just be continuously locked and never readable) but once per second shouldn't do too much harm.

On any file change just remember some kind of "dirty" flag for the source and while that flag is set try every second to read it and update the source. Reset the "dirty" flag as soon as the file read was successful.

YorVeX commented 3 years ago

I just changed my program to write to a different file first, then delete the original file and rename the new one into that place so that this all happens faster. But even with this workaround the problem still occurred, I am looking at an image shown by the image source in OBS that is differing from what is in the image file right now.

Even with my change it is not a completely atomic operation, since file deletion and move are two different operations. But the interesting thing is: I am not looking at an empty image source, just an outdated one. So the OBS update didn't occur between the delete and move. I'd rather assume OBS tried to read the file during the move operation, failed and therefore kept the image state from the last working file read. It seems that not even a file move operation is fast enough to avoid this problem.

Either way, for me this means one cannot even easily avoid this problem with control over the program/code that writes the file (which is not the case for everyone anyway). It must really be fixed on OBS side.

RytoEX commented 1 year ago

Can someone provide reliable reproduction steps for this? From a review of our last findings, we had trouble reproducing this reliably to further investigate.

YorVeX commented 1 year ago

Can someone provide reliable reproduction steps for this? From a review of our last findings, we had trouble reproducing this reliably to further investigate.

Depends on at which part you are looking at.

The TextSource may sometimes recover if content is written to the file again, but sometimes will not.

Regarding the "but sometimes will not" part: I know I certainly did have the problem that sometimes the image source would never recover anymore after a read failed, not even if the image file was overwritten again with new data. This problem I cannot reproduce anymore, just tried with a script that would keep on overwriting the underlying file of an image source every 3 seconds for 3 hours straight. It did cause the occasional load error as expected when the load attempt happened to be performed at the exact moment of the copy operation:

23:30:10.465: [image_source: 'Example Image'] loading texture 'X:/Programs/OBS Studio/bin/64bit/example.jpg'
23:30:10.466: Failed to open file 'X:/Programs/OBS Studio/bin/64bit/example.jpg': Permission denied
23:30:10.466: WIC: Failed to create IWICBitmapDecoder from file: X:/Programs/OBS Studio/bin/64bit/example.jpg
23:30:10.466: gs_image_file_init_internal: Failed to load file 'X:/Programs/OBS Studio/bin/64bit/example.jpg'
23:30:10.466: [image_source: 'Example Image'] failed to load texture 'X:/Programs/OBS Studio/bin/64bit/example.jpg'

The next overwrite would then always make the source recover (but even back then it only happened quite rarely, so maybe it was just "bad luck").

However, the first problem that after a single failed read attempt the source only recovers if the file is updated once again - that definitely is still there and IMHO is not a bug that needs reproducing, it's clearly visible from the code that it's currently designed like this. You can see in line 54 that file_timestamp is updated before the file is actually read in line 55. Now if reading the file fails (as indicated by the error in line 66 that I can also see in my logs) the field file_timestamp that controls whether OBS tries to read the file again signalizes "this file was already read", hence OBS never tries again (see lines 208 to 212 for the relevant part of the code).

Those examples are from the image source, but I checked and the code works the same for the text source.

That it's designed like that doesn't mean it's good. It doesn't make sense to me that the source would attempt every second to load a new file, but as soon as one read failed it stops these attempts, although technically for the source the file is still "new", since it didn't successfully load it so far. From my point of view it should just keep on retrying every second and only update the internal timestamp when loading succeeded.

YorVeX commented 1 year ago

If you do want to try and reproduce it nevertheless I can only help for Windows, here's a PowerShell script that will copy example1.png to example.png (which you are supposed to have an image source pointed to) so that it would update the file timestamp and trigger a reload in OBS, and then it immediately locks example.png for 1.5 seconds to make sure the file is definitely locked during the next attempt of OBS to access the file.

$source = "example1.png"
$target = "example.png"

    Copy-Item $source $target -Force
    Write-Host "Locking $target"
    $fileStream = New-Object IO.FileStream($target, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
    Start-Sleep -Milliseconds 1500
    $fileStream.Close()
    Write-Host "Lock released"

The expected outcome would be that the image source would eventually recover itself and show example.png, but instead it will stay empty forever unless you overwrite example.png once more.