Fixes a crash in wrap mode
ClosedPublic

Authored by poke1024 on Nov 6 2017, 7:15 AM.

Details

Reviewers
None
Group Reviewers
Krita
Commits
R37:ff1909aca4fc: Fix a crash in wrap-around mode
Summary

While drawing in wrap mode, QScopedPointer, which is not thread safe, is accessed without lock, which will crash:

Diff Detail

Repository
R37 Krita
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.
poke1024 created this revision.Nov 6 2017, 7:15 AM

Is it possible to use two-stage locking here?

libs/image/kis_paint_device.cc
596

Can you use two stage locking here? The strategy is called quite a lot during the merge process, so locking all the time can affect the performance. Will it be enough to move the lock into the if-block? Theoretically, it should be safe, because the strategy can never become null.

poke1024 added inline comments.Nov 7 2017, 5:37 PM
libs/image/kis_paint_device.cc
596

I don't think it's safe inside the if. (1) QScopedPointer::reset might set the pointer to 0 for a very short time while transitioning, depending on implementation details. (2) There is no memory sync barrier we can rely on, so the wrappedStrategy.data() we read in one thread might have already been deleted by another thread. The more I think about it, the whole QScopedPointer is actually a problem here. How can we guarantee that the wrappedStrategy that is deleted by one thread is no longer used by any other thread?

Hi, @poke1024!

I'm sorry for the late answer. You pointed quite an interesting problem. What do you think about proposal (see inline)?

The reason why I insisting on a lockfree manner on doing this check is that our recent tests showed that such global mutexes lead to a severe performance degradation on 8+ physical cores CPU. Right now we have 4 such mutexes in different places, and I would really want to avoid adding one more.

libs/image/kis_paint_device.cc
596

Good point! I didn't notice we delete the strategy when the wrap rect is changed. Can we do something like that?

if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) {
    QMutexLocker locker(&m_wrappedStrategyMutex);

    if (!wrappedStrategy ) {
        wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this));
    }  else if (wrappedStrategy->wrapRect() != wrapRect) {
        wrappedStrategy->setWrapRect(wrapRect); // to be implemented
    }
}

In this case KisPaintDevice never deletes the wrapped strategy. Therefore, unless a partial write happens (can it?) the first unguarded read from the scoped pointer will always be valid.

If you think the partial write can be a real problem here, it might also be safe to change the scoped pointer into QAtomicPointer and do the initialization in a lockfree manner. But there will still be a problem with calling setWrappedRect().

This revision was automatically updated to reflect the committed changes.