Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segfault / undefined behavior in Timeline::Close() #378

Closed
musteresel opened this issue Nov 27, 2019 · 24 comments
Closed

Segfault / undefined behavior in Timeline::Close() #378

musteresel opened this issue Nov 27, 2019 · 24 comments
Labels
stale This issue has not had any activity in 90 days :(

Comments

@musteresel
Copy link
Contributor

musteresel commented Nov 27, 2019

I'm seeing segmentation faults when I close OpenShot. Today I took the time to look at the stacktrace, and look, libopenshot is to blame:

terminate called without an active exception                                                                                                                              
Caught signal 6 (SIGABRT)                                                                                                                                                 
---- Unhandled Exception: Stack Trace ---- 
  /nix/store/g88xj01xv7xzdx9dy2zwga0vk56fpfbm-glibc-2.27/lib/libc.so.6 ( abort                                     + 0x141 )  [0x7f5fdac88dc1]                            
  /nix/store/q2vnbr654204znx19ivblr2vhf134mac-gcc-7.4.0-lib/lib/libstdc++.so.6 ( __gnu_cxx::__verbose_terminate_handler()  + 0x125 )  [0x7f5fd24e5f75]                    
  /nix/store/q2vnbr654204znx19ivblr2vhf134mac-gcc-7.4.0-lib/lib/libstdc++.so.6 (                                           + 0x97d66)  [0x7f5fd24e3d66]                   
  /nix/store/q2vnbr654204znx19ivblr2vhf134mac-gcc-7.4.0-lib/lib/libstdc++.so.6 (                                           + 0x97db1)  [0x7f5fd24e3db1]                   
  /nix/store/q2vnbr654204znx19ivblr2vhf134mac-gcc-7.4.0-lib/lib/libstdc++.so.6 (                                           + 0x98aef)  [0x7f5fd24e4aef]                   
  /nix/store/b94fyqq44wzyw9yhsibzazkn2mhbpl1v-libopenshot-dev/lib/libopenshot.so.17 ( openshot::Timeline::Close()               + 0x265 )  [0x7f5fc72a48a5]               
  /nix/store/b94fyqq44wzyw9yhsibzazkn2mhbpl1v-libopenshot-dev/lib/libopenshot.so.17 ( openshot::Timeline::~Timeline()           + 0x315 )  [0x7f5fc72a4cb5]               
  /nix/store/b94fyqq44wzyw9yhsibzazkn2mhbpl1v-libopenshot-dev/lib/libopenshot.so.17 ( openshot::Timeline::~Timeline()           + 0x9   )  [0x7f5fc72a4cc9]               
  /nix/store/b94fyqq44wzyw9yhsibzazkn2mhbpl1v-libopenshot-dev/lib/python3.7/site-packages/_openshot.so (                                           + 0x82a38)  [0x7f5fc7  39ba38]                                                                                                                                                                   
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 ( _PyMethodDef_RawFastCallDict              + 0x339 )  [0x7f5fdb32cdd9]              
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 ( _PyCFunction_FastCallDict                 + 0x25  )  [0x7f5fdb32ce65]              
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0xa0592)  [0x7f5fdb32d592]             
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 ( PyObject_CallFunctionObjArgs              + 0x99  )  [0x7f5fdb32d999]              
  /nix/store/b94fyqq44wzyw9yhsibzazkn2mhbpl1v-libopenshot-dev/lib/python3.7/site-packages/_openshot.so (                                           + 0x5b5d2)  [0x7f5fc7  3745d2]                                                                                                                                                                   
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0xddc45)  [0x7f5fdb36ac45]             
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0x10676d)  [0x7f5fdb39376d]            
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0xddc45)  [0x7f5fdb36ac45]             
  /nix/store/nb3v6jhh60zcn2xcgsyhkm026vvpdq5h-python3.7-PyQt5.sip-4.19.18/lib/python3.7/site-packages/PyQt5/sip.so (                                           + 0x5cc3)    [0x7f5fd02e8cc3]                                                                                                                                                        
  /nix/store/nb3v6jhh60zcn2xcgsyhkm026vvpdq5h-python3.7-PyQt5.sip-4.19.18/lib/python3.7/site-packages/PyQt5/sip.so (                                           + 0x8e64)    [0x7f5fd02ebe64]                                                                                                                                                        
  /nix/store/nb3v6jhh60zcn2xcgsyhkm026vvpdq5h-python3.7-PyQt5.sip-4.19.18/lib/python3.7/site-packages/PyQt5/sip.so (                                           + 0x15571  )  [0x7f5fd02f8571]                                                                                                                                                       
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0x1067b0)  [0x7f5fdb3937b0]            
  /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0 (                                           + 0xddc45)  [0x7f5fdb36ac45]             
  /nix/store/nb3v6jhh60zcn2xcgsyhkm026vvpdq5h-python3.7-PyQt5.sip-4.19.18/lib/python3.7/site-packages/PyQt5/sip.so (                                           + 0x5cc3)    [0x7f5fd02e8cc3]                                                                                                                                                        
  /nix/store/nb3v6jhh60zcn2xcgsyhkm026vvpdq5h-python3.7-PyQt5.sip-4.19.18/lib/python3.7/site-packages/PyQt5/sip.so (                                           + 0x8e64)    [0x7f5fd02ebe64]                                                                                                              
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Network.so.5 (                                           + 0x14b59d)  [0x7f5fcde5b59d]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QObject::event(QEvent*)                   + 0xe2  )  [0x7f5fd28a1b02]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QCoreApplication::notifyInternal2(QObject*, QEvent*)  + 0x101 )  [0x7f5fd28711a1]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*)  + 0x1a7 )  [0x7f5fd2873d77]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 (                                           + 0x2f8e23)  [0x7f5fd28cee23]
  /nix/store/9gvnlkyjabbhl26ijy4apv62dga430y5-glib-2.60.6/lib/libglib-2.0.so.0 ( g_main_context_dispatch                   + 0x2e7 )  [0x7f5fd03cc8e7]
  /nix/store/9gvnlkyjabbhl26ijy4apv62dga430y5-glib-2.60.6/lib/libglib-2.0.so.0 (                                           + 0x51b20)  [0x7f5fd03ccb20]
  /nix/store/9gvnlkyjabbhl26ijy4apv62dga430y5-glib-2.60.6/lib/libglib-2.0.so.0 ( g_main_context_iteration                  + 0x2c  )  [0x7f5fd03ccbac]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>)  + 0x5f  )  [0x7f5fd28ce43f]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>)  + 0x13a )  [0x7f5fd286f4da]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 ( QThread::exec()                           + 0x6a  )  [0x7f5fd268896a]
  /nix/store/lf18ivvikaprv6alcm2gcvsqljg1qfsg-qtbase-5.12.3/lib/libQt5Core.so.5 (                                           + 0xb4112)  [0x7f5fd268a112]
  /nix/store/g88xj01xv7xzdx9dy2zwga0vk56fpfbm-glibc-2.27/lib/libpthread.so.0 (                                           + 0x7ef7)  [0x7f5fdb273ef7]
  /nix/store/g88xj01xv7xzdx9dy2zwga0vk56fpfbm-glibc-2.27/lib/libc.so.6 ( clone                                     + 0x3f  )  [0x7f5fdad4522f]
---- End of Stack Trace ----

The culprit is - I think - quickly found in Timeline::Close():

	for (clip_itr=clips.begin(); clip_itr != clips.end(); ++clip_itr)
	{
		// Get clip object from the iterator
		Clip *clip = (*clip_itr);

		// Open or Close this clip, based on if it's intersecting or not
		update_open_clips(clip, false);
	}

What does update_open_clips()? I've removed uninteresting comments / logging but added what's happening in this case:

void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect /* FALSE for us */)
{
	bool clip_found = open_clips.count(clip); // DEFINITIVELY TRUE for us!

	if (clip_found && !does_clip_intersect) // Yep, TRUE
	{
		open_clips.erase(clip); // Ouch, erase in loop!

erase invalides the iterator (clip_itr) ... thus incrementing it is undefined behavior!

I'm fixing this as soon as I have time for it, opening this issue so that I don't forget it / it doesn't get lost.

@musteresel
Copy link
Contributor Author

Oops, no, that erase call shouldn't be the issue (it's not called on the currently iterated over vector but on a map).

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

Yeah, I was gonna say, it's modifying open_clips which isn't the same as clips. So, it should be OK.

That traceback is still pretty ugly, though. Wonder what is going on there?

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

I don't get why Timeline is doing so much stuff in its destructor. It's being removed, why is it housekeeping?

And why would it be its responsibility to do things like this?:

// Free all allocated frame mappers
std::set<FrameMapper *>::iterator frame_mapper_itr;
for (frame_mapper_itr = allocated_frame_mappers.begin(); frame_mapper_itr != allocated_frame_mappers.end(); ++frame_mapper_itr) {
// Get frame mapper object from the iterator
FrameMapper *frame_mapper = (*frame_mapper_itr);
frame_mapper->Reader(NULL);
frame_mapper->Close();
delete frame_mapper;
}
allocated_frame_mappers.clear();

If the frame_mapper is being deleted, why wouldn't the Close() be done in its own destructor (the same way Timeline::~Timeline calls its own Close()), rather than relying on its caller to do it?

None of the above is actually relevant, though.

...Anyway, clearly in your trace it's not even getting that far.

I think the traceback is a simple concurrency issue, there's waaaaaaaayy too much code that executes between this:

if (is_open)
// Auto Close if not already
Close();

and this:
// Mark timeline as closed
is_open = false;

At the very least, I think it should look something more like this:

Timeline::~Timeline() {
	if (is_open) {
		// Auto Close if not already
                is_open = false;
		Close();
        }

...To reduce the chances that two threads will both try to close the same Timeline. (If not actual locking to ensure it doesn't happen.)

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

Also, Timeline::~Timeline() calls Timeline::Close(), which in turn uses Timeline::update_open_clips()... which potentially will open "missing" clips even as the Timeline is being destroyed!

else if (!clip_found && does_clip_intersect)
{
// Add clip to 'opened' list, because it's missing
open_clips[clip] = clip;
try {
// Open the clip
clip->Open();
} catch (const InvalidFile & e) {

Clearly, the Timeline destructor should not be doing any of that.

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

The problem with Close() methods in libopenshot is that they're meant to be state transitions — an object can Open() and Close() and Open() and Close() multiple times over the course of its lifetime. As a result, Close() actually does a lot of work to preserve a certain internal state, in anticipation of a later Open() to follow.

That's all well and good, but it means that calling Close() from the classes' destructors is a terrible idea. It's overcomplicated and results in a lot of time being spent rearranging deck chairs on the Titanic.

Instead of calling Close() and carefully preparing for future operations that will never execute, the destructors should just clean house, delete or free whatever they need to, and get out as quickly as possible.

The whole reason for this:

FrameMapper *frame_mapper = (*frame_mapper_itr);
frame_mapper->Reader(NULL);
frame_mapper->Close();
delete frame_mapper;

was apparently a fix to prevent previous destructor crashes, because FrameMapper::~FrameMapper() does call its own Close() — and there again, Close() is doing lots of complicated things if reader is non-null:
// Close the internal reader
void FrameMapper::Close()
{
if (reader)
{
// Create a scoped lock, allowing only a single thread to run the following code at one time
const GenericScopedLock<CriticalSection> lock(getFrameCriticalSection);
ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Close");
// Close internal reader
reader->Close();
// Clear the fields & frames lists
fields.clear();
frames.clear();
// Mark as dirty
is_dirty = true;
// Clear cache
final_cache.Clear();
// Deallocate resample buffer
if (avr) {
SWR_CLOSE(avr);
SWR_FREE(&avr);
avr = NULL;
}
}
}

...But am I wrong in thinking that, rather than NULL-ing reader before calling FrameMapper::Close() before destroying the FrameMapper, it would make more sense for FrameMapper::~FrameMapper() to just NOT call Close(), and instead directly clean up only what it needs to so it can quickly exit? Despite its name Close() actually creates state, which never makes sense in a destructor.

@musteresel
Copy link
Contributor Author

Hm ... indeed, all that Close()ing is no good ... far too much complicated. Most resources should be automatically managed eitherway (std::shared_ptr instead of new/delete).

But your guess regarding a concurrency issue ... I don't think that's the problem here. I was running in gdb and set a breakpoint at Close() ... and only one thread hit that breakpoint, twice. One time during loading (called from SetJsonValue()) and the other time the one where the segfault happens.

This also rules out my suspected "double" destructor call.

What seems to be happening - looking at the assembly - is a call to a computed address. Only that this address is far off from where it should be? I've attached the disassembly of the full function below and marked the line where the segfault happens (with ### in the beginning).

Perhaps this is the final_cache->Clear() call? I've added an assert(final_cache) before that line ... it's at least not null, though.

000000000000a880 <openshot::Timeline::Close()>:
    a880:	55                   	push   %rbp
    a881:	48 89 e5             	mov    %rsp,%rbp
    a884:	41 57                	push   %r15
    a886:	41 56                	push   %r14
    a888:	41 55                	push   %r13
    a88a:	41 54                	push   %r12
    a88c:	4c 8d 65 80          	lea    -0x80(%rbp),%r12
    a890:	53                   	push   %rbx
    a891:	48 89 fb             	mov    %rdi,%rbx
    a894:	48 81 ec 18 01 00 00 	sub    $0x118,%rsp
    a89b:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
    a8a2:	00 00 
    a8a4:	48 89 45 c8          	mov    %rax,-0x38(%rbp)
    a8a8:	31 c0                	xor    %eax,%eax
    a8aa:	e8 00 00 00 00       	callq  a8af <openshot::Timeline::Close()+0x2f>
    a8af:	48 8d 4d a0          	lea    -0x60(%rbp),%rcx
    a8b3:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a8ba <openshot::Timeline::Close()+0x3a>
    a8ba:	48 89 85 c0 fe ff ff 	mov    %rax,-0x140(%rbp)
    a8c1:	48 8d 41 10          	lea    0x10(%rcx),%rax
    a8c5:	48 89 d6             	mov    %rdx,%rsi
    a8c8:	48 89 cf             	mov    %rcx,%rdi
    a8cb:	48 89 8d c8 fe ff ff 	mov    %rcx,-0x138(%rbp)
    a8d2:	48 89 45 a0          	mov    %rax,-0x60(%rbp)
    a8d6:	e8 a5 57 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a8db:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a8e2 <openshot::Timeline::Close()+0x62>
    a8e2:	49 8d 44 24 10       	lea    0x10(%r12),%rax
    a8e7:	4c 89 e7             	mov    %r12,%rdi
    a8ea:	48 89 d6             	mov    %rdx,%rsi
    a8ed:	48 89 45 80          	mov    %rax,-0x80(%rbp)
    a8f1:	e8 8a 57 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a8f6:	4c 8d 95 60 ff ff ff 	lea    -0xa0(%rbp),%r10
    a8fd:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a904 <openshot::Timeline::Close()+0x84>
    a904:	49 8d 42 10          	lea    0x10(%r10),%rax
    a908:	48 89 d6             	mov    %rdx,%rsi
    a90b:	4c 89 d7             	mov    %r10,%rdi
    a90e:	4c 89 95 d8 fe ff ff 	mov    %r10,-0x128(%rbp)
    a915:	48 89 85 60 ff ff ff 	mov    %rax,-0xa0(%rbp)
    a91c:	e8 5f 57 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a921:	4c 8d 9d 40 ff ff ff 	lea    -0xc0(%rbp),%r11
    a928:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a92f <openshot::Timeline::Close()+0xaf>
    a92f:	49 8d 43 10          	lea    0x10(%r11),%rax
    a933:	48 89 d6             	mov    %rdx,%rsi
    a936:	4c 89 df             	mov    %r11,%rdi
    a939:	4c 89 9d d0 fe ff ff 	mov    %r11,-0x130(%rbp)
    a940:	48 89 85 40 ff ff ff 	mov    %rax,-0xc0(%rbp)
    a947:	e8 34 57 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a94c:	4c 8d ad 20 ff ff ff 	lea    -0xe0(%rbp),%r13
    a953:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a95a <openshot::Timeline::Close()+0xda>
    a95a:	49 8d 45 10          	lea    0x10(%r13),%rax
    a95e:	48 89 d6             	mov    %rdx,%rsi
    a961:	4c 89 ef             	mov    %r13,%rdi
    a964:	48 89 85 20 ff ff ff 	mov    %rax,-0xe0(%rbp)
    a96b:	e8 10 57 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a970:	4c 8d b5 00 ff ff ff 	lea    -0x100(%rbp),%r14
    a977:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a97e <openshot::Timeline::Close()+0xfe>
    a97e:	49 8d 46 10          	lea    0x10(%r14),%rax
    a982:	48 89 d6             	mov    %rdx,%rsi
    a985:	4c 89 f7             	mov    %r14,%rdi
    a988:	48 89 85 00 ff ff ff 	mov    %rax,-0x100(%rbp)
    a98f:	e8 ec 56 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a994:	4c 8d bd e0 fe ff ff 	lea    -0x120(%rbp),%r15
    a99b:	48 8d 15 00 00 00 00 	lea    0x0(%rip),%rdx        # a9a2 <openshot::Timeline::Close()+0x122>
    a9a2:	49 8d 47 10          	lea    0x10(%r15),%rax
    a9a6:	48 8d 72 f1          	lea    -0xf(%rdx),%rsi
    a9aa:	4c 89 ff             	mov    %r15,%rdi
    a9ad:	48 89 85 e0 fe ff ff 	mov    %rax,-0x120(%rbp)
    a9b4:	e8 c7 56 ff ff       	callq  80 <void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag) [clone .isra.135]>
    a9b9:	f3 0f 10 2d 00 00 00 	movss  0x0(%rip),%xmm5        # a9c1 <openshot::Timeline::Close()+0x141>
    a9c0:	00 
    a9c1:	4c 8b 8d d8 fe ff ff 	mov    -0x128(%rbp),%r9
    a9c8:	4c 8b 85 d0 fe ff ff 	mov    -0x130(%rbp),%r8
    a9cf:	48 8b bd c0 fe ff ff 	mov    -0x140(%rbp),%rdi
    a9d6:	4c 89 e9             	mov    %r13,%rcx
    a9d9:	ff b5 c8 fe ff ff    	pushq  -0x138(%rbp)
    a9df:	0f 28 e5             	movaps %xmm5,%xmm4
    a9e2:	41 54                	push   %r12
    a9e4:	0f 28 dd             	movaps %xmm5,%xmm3
    a9e7:	0f 28 d5             	movaps %xmm5,%xmm2
    a9ea:	4c 89 f2             	mov    %r14,%rdx
    a9ed:	0f 28 cd             	movaps %xmm5,%xmm1
    a9f0:	4c 89 fe             	mov    %r15,%rsi
    a9f3:	0f 28 c5             	movaps %xmm5,%xmm0
    a9f6:	e8 00 00 00 00       	callq  a9fb <openshot::Timeline::Close()+0x17b>
    a9fb:	48 8b bd e0 fe ff ff 	mov    -0x120(%rbp),%rdi
    aa02:	49 83 c7 10          	add    $0x10,%r15
    aa06:	58                   	pop    %rax
    aa07:	5a                   	pop    %rdx
    aa08:	4c 39 ff             	cmp    %r15,%rdi
    aa0b:	74 05                	je     aa12 <openshot::Timeline::Close()+0x192>
    aa0d:	e8 00 00 00 00       	callq  aa12 <openshot::Timeline::Close()+0x192>
    aa12:	48 8b bd 00 ff ff ff 	mov    -0x100(%rbp),%rdi
    aa19:	49 83 c6 10          	add    $0x10,%r14
    aa1d:	4c 39 f7             	cmp    %r14,%rdi
    aa20:	74 05                	je     aa27 <openshot::Timeline::Close()+0x1a7>
    aa22:	e8 00 00 00 00       	callq  aa27 <openshot::Timeline::Close()+0x1a7>
    aa27:	48 8b bd 20 ff ff ff 	mov    -0xe0(%rbp),%rdi
    aa2e:	49 83 c5 10          	add    $0x10,%r13
    aa32:	4c 39 ef             	cmp    %r13,%rdi
    aa35:	74 05                	je     aa3c <openshot::Timeline::Close()+0x1bc>
    aa37:	e8 00 00 00 00       	callq  aa3c <openshot::Timeline::Close()+0x1bc>
    aa3c:	48 8b 85 d0 fe ff ff 	mov    -0x130(%rbp),%rax
    aa43:	48 8b bd 40 ff ff ff 	mov    -0xc0(%rbp),%rdi
    aa4a:	48 83 c0 10          	add    $0x10,%rax
    aa4e:	48 39 c7             	cmp    %rax,%rdi
    aa51:	74 05                	je     aa58 <openshot::Timeline::Close()+0x1d8>
    aa53:	e8 00 00 00 00       	callq  aa58 <openshot::Timeline::Close()+0x1d8>
    aa58:	48 8b 85 d8 fe ff ff 	mov    -0x128(%rbp),%rax
    aa5f:	48 8b bd 60 ff ff ff 	mov    -0xa0(%rbp),%rdi
    aa66:	48 83 c0 10          	add    $0x10,%rax
    aa6a:	48 39 c7             	cmp    %rax,%rdi
    aa6d:	74 05                	je     aa74 <openshot::Timeline::Close()+0x1f4>
    aa6f:	e8 00 00 00 00       	callq  aa74 <openshot::Timeline::Close()+0x1f4>
    aa74:	48 8b 7d 80          	mov    -0x80(%rbp),%rdi
    aa78:	49 83 c4 10          	add    $0x10,%r12
    aa7c:	4c 39 e7             	cmp    %r12,%rdi
    aa7f:	74 05                	je     aa86 <openshot::Timeline::Close()+0x206>
    aa81:	e8 00 00 00 00       	callq  aa86 <openshot::Timeline::Close()+0x206>
    aa86:	48 8b 85 c8 fe ff ff 	mov    -0x138(%rbp),%rax
    aa8d:	48 8b 7d a0          	mov    -0x60(%rbp),%rdi
    aa91:	48 83 c0 10          	add    $0x10,%rax
    aa95:	48 39 c7             	cmp    %rax,%rdi
    aa98:	74 05                	je     aa9f <openshot::Timeline::Close()+0x21f>
    aa9a:	e8 00 00 00 00       	callq  aa9f <openshot::Timeline::Close()+0x21f>
    aa9f:	48 8b 83 48 01 00 00 	mov    0x148(%rbx),%rax
    aaa6:	4c 8d a3 48 01 00 00 	lea    0x148(%rbx),%r12
    aaad:	4c 39 e0             	cmp    %r12,%rax
    aab0:	74 1f                	je     aad1 <openshot::Timeline::Close()+0x251>
    aab2:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
    aab8:	48 8b 70 10          	mov    0x10(%rax),%rsi
    aabc:	49 89 c6             	mov    %rax,%r14
    aabf:	31 d2                	xor    %edx,%edx
    aac1:	48 89 df             	mov    %rbx,%rdi
    aac4:	e8 00 00 00 00       	callq  aac9 <openshot::Timeline::Close()+0x249>
    aac9:	49 8b 06             	mov    (%r14),%rax
    aacc:	4c 39 e0             	cmp    %r12,%rax
    aacf:	75 e7                	jne    aab8 <openshot::Timeline::Close()+0x238>
    aad1:	48 8b bb c0 01 00 00 	mov    0x1c0(%rbx),%rdi
    aad8:	c6 83 40 01 00 00 00 	movb   $0x0,0x140(%rbx)
    aadf:	48 8b 07             	mov    (%rdi),%rax
### aae2:	ff 50 08             	callq  *0x8(%rax)
    aae5:	48 8b 45 c8          	mov    -0x38(%rbp),%rax
    aae9:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
    aaf0:	00 00 
    aaf2:	75 0f                	jne    ab03 <openshot::Timeline::Close()+0x283>
    aaf4:	48 8d 65 d8          	lea    -0x28(%rbp),%rsp
    aaf8:	5b                   	pop    %rbx
    aaf9:	41 5c                	pop    %r12
    aafb:	41 5d                	pop    %r13
    aafd:	41 5e                	pop    %r14
    aaff:	41 5f                	pop    %r15
    ab01:	5d                   	pop    %rbp
    ab02:	c3                   	retq   
    ab03:	e8 00 00 00 00       	callq  ab08 <openshot::Timeline::Close()+0x288>
    ab08:	48 8b bd e0 fe ff ff 	mov    -0x120(%rbp),%rdi
    ab0f:	49 83 c7 10          	add    $0x10,%r15
    ab13:	48 89 c3             	mov    %rax,%rbx
    ab16:	4c 39 ff             	cmp    %r15,%rdi
    ab19:	74 05                	je     ab20 <openshot::Timeline::Close()+0x2a0>
    ab1b:	e8 00 00 00 00       	callq  ab20 <openshot::Timeline::Close()+0x2a0>
    ab20:	48 8b bd 00 ff ff ff 	mov    -0x100(%rbp),%rdi
    ab27:	49 83 c6 10          	add    $0x10,%r14
    ab2b:	4c 39 f7             	cmp    %r14,%rdi
    ab2e:	74 05                	je     ab35 <openshot::Timeline::Close()+0x2b5>
    ab30:	e8 00 00 00 00       	callq  ab35 <openshot::Timeline::Close()+0x2b5>
    ab35:	48 8b bd 20 ff ff ff 	mov    -0xe0(%rbp),%rdi
    ab3c:	49 83 c5 10          	add    $0x10,%r13
    ab40:	4c 39 ef             	cmp    %r13,%rdi
    ab43:	74 05                	je     ab4a <openshot::Timeline::Close()+0x2ca>
    ab45:	e8 00 00 00 00       	callq  ab4a <openshot::Timeline::Close()+0x2ca>
    ab4a:	48 8b 95 d0 fe ff ff 	mov    -0x130(%rbp),%rdx
    ab51:	48 8b bd 40 ff ff ff 	mov    -0xc0(%rbp),%rdi
    ab58:	48 83 c2 10          	add    $0x10,%rdx
    ab5c:	48 39 d7             	cmp    %rdx,%rdi
    ab5f:	74 05                	je     ab66 <openshot::Timeline::Close()+0x2e6>
    ab61:	e8 00 00 00 00       	callq  ab66 <openshot::Timeline::Close()+0x2e6>
    ab66:	48 8b 95 d8 fe ff ff 	mov    -0x128(%rbp),%rdx
    ab6d:	48 8b bd 60 ff ff ff 	mov    -0xa0(%rbp),%rdi
    ab74:	48 83 c2 10          	add    $0x10,%rdx
    ab78:	48 39 d7             	cmp    %rdx,%rdi
    ab7b:	74 05                	je     ab82 <openshot::Timeline::Close()+0x302>
    ab7d:	e8 00 00 00 00       	callq  ab82 <openshot::Timeline::Close()+0x302>
    ab82:	48 8b 7d 80          	mov    -0x80(%rbp),%rdi
    ab86:	49 83 c4 10          	add    $0x10,%r12
    ab8a:	4c 39 e7             	cmp    %r12,%rdi
    ab8d:	74 05                	je     ab94 <openshot::Timeline::Close()+0x314>
    ab8f:	e8 00 00 00 00       	callq  ab94 <openshot::Timeline::Close()+0x314>
    ab94:	48 8b 85 c8 fe ff ff 	mov    -0x138(%rbp),%rax
    ab9b:	48 8b 7d a0          	mov    -0x60(%rbp),%rdi
    ab9f:	48 83 c0 10          	add    $0x10,%rax
    aba3:	48 39 c7             	cmp    %rax,%rdi
    aba6:	74 05                	je     abad <openshot::Timeline::Close()+0x32d>
    aba8:	e8 00 00 00 00       	callq  abad <openshot::Timeline::Close()+0x32d>
    abad:	48 89 df             	mov    %rbx,%rdi
    abb0:	e8 00 00 00 00       	callq  abb5 <openshot::Timeline::Close()+0x335>
    abb5:	48 89 c3             	mov    %rax,%rbx
    abb8:	e9 63 ff ff ff       	jmpq   ab20 <openshot::Timeline::Close()+0x2a0>
    abbd:	48 89 c3             	mov    %rax,%rbx
    abc0:	e9 70 ff ff ff       	jmpq   ab35 <openshot::Timeline::Close()+0x2b5>
    abc5:	48 89 c3             	mov    %rax,%rbx
    abc8:	eb 80                	jmp    ab4a <openshot::Timeline::Close()+0x2ca>
    abca:	48 89 c3             	mov    %rax,%rbx
    abcd:	eb 97                	jmp    ab66 <openshot::Timeline::Close()+0x2e6>
    abcf:	48 89 c3             	mov    %rax,%rbx
    abd2:	eb ae                	jmp    ab82 <openshot::Timeline::Close()+0x302>
    abd4:	48 89 c3             	mov    %rax,%rbx
    abd7:	eb bb                	jmp    ab94 <openshot::Timeline::Close()+0x314>
    abd9:	90                   	nop
    abda:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)


@musteresel
Copy link
Contributor Author

musteresel commented Nov 28, 2019

It's that final_cache->Clear(), though.

Openshot somewhere apparently calls (through the pyhton interface) Timeline::SetCache:

Thread 1 ".openshot-qt-wr" hit Breakpoint 2, openshot::Timeline::SetCache (this=0x1d61580, new_cache=0x372d0e0) at /build/libopenshot/src/Timeline.cpp:969
969     /build/libopenshot/src/Timeline.cpp: No such file or directory.
(gdb) bt
#0  openshot::Timeline::SetCache (this=0x1d61580, new_cache=0x372d0e0) at /build/libopenshot/src/Timeline.cpp:969
#1  0x00007f62418fa690 in _wrap_Timeline_SetCache (args=0x7f62385297d0)
    at /build/libopenshot/build/src/bindings/python/CMakeFiles/pyopenshot.dir/openshotPYTHON_wrap.cxx:38917
#2  0x00007f62558ca693 in PyCFunction_Call () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
....

Then, when I close Openshot I first get:

Thread 1 ".openshot-qt-wr" hit Breakpoint 1, openshot::CacheBase::~CacheBase (this=0x372d0e0, __in_chrg=<optimized out>)
    at /build/libopenshot/src/../include/CacheBase.h:116
116     in /build/libopenshot/src/../include/CacheBase.h
(gdb) bt
#0  openshot::CacheBase::~CacheBase (this=0x372d0e0, __in_chrg=<optimized out>) at /build/libopenshot/src/../include/CacheBase.h:116
#1  0x00007f62414ecb8e in openshot::CacheMemory::~CacheMemory (this=0x372d0e0, __in_chrg=<optimized out>) at /build/libopenshot/src/CacheMemory.cpp:53
#2  0x00007f62414ecbae in openshot::CacheMemory::~CacheMemory (this=0x372d0e0, __in_chrg=<optimized out>) at /build/libopenshot/src/CacheMemory.cpp:62
#3  0x00007f624189e174 in _wrap_delete_CacheMemory (args=0x7f6238571a10)
    at /build/libopenshot/build/src/bindings/python/CMakeFiles/pyopenshot.dir/openshotPYTHON_wrap.cxx:12827
#4  0x00007f62558c8dd9 in _PyMethodDef_RawFastCallDict () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#5  0x00007f62558c8e65 in _PyCFunction_FastCallDict () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#6  0x00007f62558c9592 in object_vacall () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#7  0x00007f62558c9999 in PyObject_CallFunctionObjArgs () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#8  0x00007f62418821b8 in SwigPyObject_dealloc (v=0x7f6238523d50) at /build/libopenshot/build/src/bindings/python/CMakeFiles/pyopenshot.dir/openshotPYTHON_wrap.cxx:1720
#9  0x00007f6255906c45 in dict_dealloc () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#10 0x00007f625592f76d in subtype_dealloc () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
#11 0x00007f6255906c45 in dict_dealloc () from /nix/store/xw06hkwpxyjxil3d5h374imxp3qhkscq-python3-3.7.4/lib/libpython3.7m.so.1.0
...

Which calls the destructor of the object which was passed to Timeline::SetCache() ... and only then I hit the Timeline::~Timeline() destructor.

So the issue is that Openshot drops the reference to the cache object, Python calls the cache object's destructor, then later calls timelines destructor which expects the cache object to still be valid.

libopenshot isn't to blame then, after all (only for that insecure interface ... we should change this)

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

@musteresel Interesting! Yeah, in looking at the code — this is one of five calls to SetCache in OpenShot, the other four are all in main_window.py — this is a risky way to manage the cache from Python:

src/windows/export.py#L786-L791

        # Set MaxSize (so we don't have any downsampling)
        self.timeline.SetMaxSize(video_settings.get("width"), video_settings.get("height"))

        # Set lossless cache settings (temporarily)
        export_cache_object = openshot.CacheMemory(500)
        self.timeline.SetCache(export_cache_object)

...since Python will keep a reference to export_cache_object, which it should never have in the first place. Seems better if openshot::Timeline had an AllocMemCache() method or whatever, that takes max_bytes and just creates the CacheMemory object internally... Python should never see that object.

(Does kind of feel like libopenshot's fault, though. The Python code never actually does anything destructive or wrong... it just handles an object it's forced to handle due to the design of the current Timeline API.) 😉

Still, in the immediate term it might be enough to just del export_cache_object from Python, after passing it to openshot.Timeline. Not sure if that'd work, but it's one or the other: Either we'd just be deleting a reference safely, which should fix this, or it'll destroy the object way earlier and cause even more spectacular crashes.

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

ARRRRGH! Of course, that won't help us with this code, where OpenShot's Python code not only sees the cache objects, but actually does the work of swapping them! 😬

src/windows/main_window.py#L1028-L1035

        # Save current cache object and create a new CacheMemory object (ignore quality and scale prefs)
        old_cache_object = self.cache_object
        new_cache_object = openshot.CacheMemory(settings.get_settings().get("cache-limit-mb") * 1024 * 1024)
        self.timeline_sync.timeline.SetCache(new_cache_object)

        # Set MaxSize to full project resolution and clear preview cache so we get a full resolution frame
        self.timeline_sync.timeline.SetMaxSize(app.project.get("width"), app.project.get("height"))
        self.cache_object.Clear()

src/windows/main_window.py#L1052-L1059

  # Reset the MaxSize to match the preview and reset the preview cache
        viewport_rect = self.videoPreview.centeredViewport(self.videoPreview.width(), self.videoPreview.height())
        self.timeline_sync.timeline.SetMaxSize(viewport_rect.width(), viewport_rect.height())
        self.cache_object.Clear()
        self.timeline_sync.timeline.SetCache(old_cache_object)
        self.cache_object = old_cache_object
        old_cache_object = None
        new_cache_object = None

Or main_window.py's InitCache() method, which not only handles cache objects, but intentionally hangs on to references to them:

src/windows/main_window.py#L2380-L2412

    def InitCacheSettings(self):
        """Set the correct cache settings for the timeline"""
        # Load user settings
        s = settings.get_settings()
        log.info("InitCacheSettings")
        log.info("cache-mode: %s" % s.get("cache-mode"))
        log.info("cache-limit-mb: %s" % s.get("cache-limit-mb"))

        # Get MB limit of cache (and convert to bytes)
        cache_limit = s.get("cache-limit-mb") * 1024 * 1024 # Convert MB to Bytes

        # Clear old cache
        new_cache_object = None
        if s.get("cache-mode") == "CacheMemory":
            # Create CacheMemory object, and set on timeline
            log.info("Creating CacheMemory object with %s byte limit" % cache_limit)
            new_cache_object = openshot.CacheMemory(cache_limit)
            self.timeline_sync.timeline.SetCache(new_cache_object)

        elif s.get("cache-mode") == "CacheDisk":
            # Create CacheDisk object, and set on timeline
            log.info("Creating CacheDisk object with %s byte limit at %s" % (cache_limit, info.PREVIEW_CACHE_PATH))
            image_format = s.get("cache-image-format")
            image_quality = s.get("cache-quality")
            image_scale = s.get("cache-scale")
            new_cache_object = openshot.CacheDisk(info.PREVIEW_CACHE_PATH, image_format, image_quality, image_scale, cache_limit)
            self.timeline_sync.timeline.SetCache(new_cache_object)

        # Clear old cache before it goes out of scope
        if self.cache_object:
            self.cache_object.Clear()
        # Update cache reference, so it doesn't go out of scope
        self.cache_object = new_cache_object

@musteresel
Copy link
Contributor Author

Hm ... passing in a pointer to an externally created cache object (which implements some interface) is IMO a good thing. The issue is we need to handle ownership more clearly, or rather in a way such that it's easier for the python code (and other C++ code) to "do the right thing".

We could use a std::weak_ptr instead of a plain pointer. That way we can upgrade it to a std::shared_ptr (which as long as we hold it won't let the cache get destructed) when it's used. The (big) drawback is that we'd force users to provide us the buffer in std::shared_ptr in the first place, e.g. making code like this impossible (or extremely hard to write):

MyCacheObject obj;
some_timeline.SetCache(&obj);
// do work

I think the best idea would be to just not use the cache when destructing the Timeline object. It's not really Timelines responsibility to keep that (user supplied) cache clean when it's done with it, the user can do that on their own if they want to reuse the cache for e.g. another timeline.

So, i'd say to the user we present this:

// If new_cache != NULL, sets new_cache as the cache to use.
// If new_cache == NULL, then Timeline uses no or an internal cache.
//
// Returns a previously set cache or NULL if no cache was set previously. Returns also NULL when the previous cache is an internal one.
CacheBase * Timeline::SetCache(CacheBase * new_cache);

So the state of Timeline would be:

Timeline tl = /* construction */
// tl now uses an internal or no cache, no deal to the external code
CacheBase * old = tl.SetCache(new MyAwesomeCache());
assert(old == nullptr)
// tl now uses the instance of MyAwesomeCache
old = tl.SetCache(nullptr);
// old is the pointer returned above by new
delete old;
// tl uses an internal or no cache

@musteresel
Copy link
Contributor Author

musteresel commented Nov 28, 2019

Basically we don't take ownership of external cache objects, but allow "freeing" the timeline from them.

And in Timeline::~Timeline() the cache shouldn't be used at all. That way the order of destruction doesn't matter .. the Python code just needs to make sure to not call any member functions of a timeline after it dropped the reference to the cache.

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 28, 2019

Hrm. That still feels risky to me, but if you think it can work, I'm game.

@musteresel
Copy link
Contributor Author

I've been looking how Timeline uses that cache and ... Eh .... what?

// GENERATE CACHE FOR CLIPS (IN FRAME # SEQUENCE)
// Determine all clip frames, and request them in order (to keep resampled audio in sequence)
for (int64_t frame_number = requested_frame; frame_number < requested_frame + minimum_frames; frame_number++)
{
// Loop through clips
for (int clip_index = 0; clip_index < nearby_clips.size(); clip_index++)
{
// Get clip object from the iterator
Clip *clip = nearby_clips[clip_index];
long clip_start_position = round(clip->Position() * info.fps.ToDouble()) + 1;
long clip_end_position = round((clip->Position() + clip->Duration()) * info.fps.ToDouble()) + 1;
bool does_clip_intersect = (clip_start_position <= frame_number && clip_end_position >= frame_number);
if (does_clip_intersect)
{
// Get clip frame #
long clip_start_frame = (clip->Start() * info.fps.ToDouble()) + 1;
long clip_frame_number = frame_number - clip_start_position + clip_start_frame;
// Cache clip object
clip->GetFrame(clip_frame_number);
}
}
}

Line 797 ... it just throws new Frames away? As far as I can see Clip does not put anything into a cache ..?

Only later it adds some frame to the cache ... after numerous other stuff it's doing.

The big question here is: Who is responsible for working with that cache? Do we want / need a cache per timeline? Per clip?

@musteresel
Copy link
Contributor Author

musteresel commented Nov 28, 2019

Hrm. That still feels risky to me, but if you think it can work, I'm game.

Yes ... I just think the ability to use a cache other than the memory backed cache is important. The CacheDisk is an interesting thing and might be necessary /helpful for some projects?

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

Hrm. That still feels risky to me, but if you think it can work, I'm game.

Yes ... I just think the ability to use a cache other than the memory backed cache is important. The CacheDisk is an interesting thing and might be necessary /helpful for some projects?

Oh, I don't disagree — and I don't think the ability needs to be interfered with. As you say, Timeline should just not take ownership of external cache references. That's definitely step 1.

But I think there's also room for convenience methods that just configure (and/or enable?) that internal cache you mention here:

// If new_cache == NULL, then Timeline uses no or an internal cache.

...So that callers, especially callers in other languages, don't necessarily have to manage it for themselves. (OpenShot's Python code would seem to be an ideal candidate — It doesn't do ANYTHING with the cache other than call one part of the libopenshot API to allocate it, and another part to assign it to the Timeline. Is there any advantage to having the Python code in there playing middleman? If not, my argument would be: Then get it out!)

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

The CacheDisk is an interesting thing and might be necessary /helpful for some projects?

Even OpenShot uses CacheDisk, BTW — I was definitely thinking that there'd be an AllocDiskCache() method as well, not just AllocMemCache().

In both cases, they'd take the same arguments as the cache objects' constructors, but instead of returning the object to the caller, Timeline would keep track of it, and it would own and manage the allocation (including doing cleanup from the destructor).

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

The biggest complication to something like what I suggested would be actionSaveFrameTrigger(). Which is where OpenShot ends up juggling temporary cache objects, because it completely reconfigures the previewer so it can grab a full-resolution image for the current frame. That does rely on OpenShot itself having access to (and ownership of) the cache.

(Though... should it? Maybe there's a cleaner approach to that, anyway.)

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

@musteresel

While we're on the topic that started this all off originally, I'm trying to grok how the bits of CacheDisk::Remove() and CacheMemory::Remove() that use an iterator to .erase() arbitrary members from a std::deque<> don't run afoul of that same invalidation concern.

// Remove range of frames
void CacheDisk::Remove(int64_t start_frame_number, int64_t end_frame_number)
{
// Create a scoped lock, to protect the cache from multiple threads
const GenericScopedLock<CriticalSection> lock(*cacheCriticalSection);
// Loop through frame numbers
std::deque<int64_t>::iterator itr;
for(itr = frame_numbers.begin(); itr != frame_numbers.end();)
{
//deque<int64_t>::iterator current = itr++;
if (*itr >= start_frame_number && *itr <= end_frame_number)
{
// erase frame number
itr = frame_numbers.erase(itr);
} else
itr++;
}

// Remove range of frames
void CacheMemory::Remove(int64_t start_frame_number, int64_t end_frame_number)
{
// Create a scoped lock, to protect the cache from multiple threads
const GenericScopedLock<CriticalSection> lock(*cacheCriticalSection);
// Loop through frame numbers
std::deque<int64_t>::iterator itr;
for(itr = frame_numbers.begin(); itr != frame_numbers.end();)
{
if (*itr >= start_frame_number && *itr <= end_frame_number)
{
// erase frame number
itr = frame_numbers.erase(itr);
}else
itr++;
}

Documentation seems to conflict / be unclear on whether that's actually OK (or I'm not grasping it fully). The invalidation note for erase in the std::deque<> reference reads:

Invalidated

If erasing at begin - only erased elements
If erasing at end - only erased elements and the past-the-end iterator
Otherwise - all iterators are invalidated (including the past-the-end iterator).

Buut, exactly that pattern is part of the example code for std::deque<>::erase(), where arbitrary members are .erase()d from a std::deque, not all of them at the beginning or end:

    // Erase all even numbers (C++11 and later)
    for (auto it = c.begin(); it != c.end(); ) {
        if (*it % 2 == 0) {
            it = c.erase(it);
        } else {
            ++it;
        }
    }

I guess, because .erase() returns an iterator, that returned instance isn't invalidated by the .erase() operation?

@musteresel
Copy link
Contributor Author

Exactly; the returned iterator after these erase calls in the middle is the only valid iterator:

auto this_one_is_valid = c.erase(it);
// "it" invalid
it =this_one_is_valid; 
// now "it" is valid again

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

@ferdnyc
Copy link
Contributor

ferdnyc commented Nov 29, 2019

(As an aside, can I just say... while I'm generally of the mindset that braceless conditional blocks are evil, representing a false economy that only leads to later bugs, I can accept that under certain circumstances they make code more concise.* But the idea of pairing a braced if with an unbraced else is just horrifying to me.)

IOW: THIS bit of code was born in the pits of hell, style-wise, and I reject it on principle! 😉

if (*itr >= start_frame_number && *itr <= end_frame_number)
{
// erase frame number
itr = frame_numbers.erase(itr);
}else
itr++;

* – (Though in truth I find that opinion is held mostly by proponents of Allman/GNU bracing, where a block's opening brace adds an entire extra line to the listing. Whereas just using K&R bracing avoids that issue, and if you always use braces, the open brace doesn't need to call attention to itself by being on a separate line.)

@stale
Copy link

stale bot commented Mar 12, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale This issue has not had any activity in 90 days :( label Mar 12, 2020
@ferdnyc
Copy link
Contributor

ferdnyc commented Mar 14, 2020

Hands off, bot!

@stale stale bot removed the stale This issue has not had any activity in 90 days :( label Mar 14, 2020
@stale
Copy link

stale bot commented Jun 12, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale This issue has not had any activity in 90 days :( label Jun 12, 2020
@stale stale bot closed this as completed Jun 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale This issue has not had any activity in 90 days :(
Projects
None yet
Development

No branches or pull requests

2 participants