1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3 * Copyright 2019 Pelican Mapping
4 * http://osgearth.org
5 *
6 * osgEarth is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
16 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17 * IN THE SOFTWARE.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21 */
22 
23 #include <osgEarth/catch.hpp>
24 #include <osgEarth/ThreadingUtils>
25 
26 using namespace osgEarth;
27 
28 
29 namespace ReadWriteMutexTest
30 {
31     osgEarth::Threading::ReadWriteMutex mutex;
32     bool readLock1 = false;
33     bool readLock2 = false;
34     bool attemptingWriteLock = false;
35     bool writeLock = false;
36 
37 
38     class Thread1 : public OpenThreads::Thread
39     {
40     public:
Thread1()41         Thread1()
42         {
43 
44         }
45 
run()46         void run()
47         {
48             // Thread one takes the first read lock.
49             mutex.readLock();
50             readLock1 = true;
51 
52             // Wait for the write lock to happen in thread 2
53             while (!attemptingWriteLock)
54             {
55                 OpenThreads::Thread::YieldCurrentThread();
56             }
57 
58             // The write lock is being attempted, sleep for awhile to make sure it actually tries to get the write lock.
59             OpenThreads::Thread::microSleep(2e6);
60 
61             // Take a second read lock
62             mutex.readLock();
63             readLock2 = true;
64 
65             // Unlock both of our read locks
66             mutex.readUnlock();
67             mutex.readUnlock();
68         }
69     };
70 
71 
72 
73     class Thread2 : public osg::Referenced, public OpenThreads::Thread
74     {
75     public:
Thread2()76         Thread2()
77         {
78 
79         }
80 
run()81         void run()
82         {
83             // Wait for thread1 to grab the read lock.
84             while (!readLock1)
85             {
86                 OpenThreads::Thread::YieldCurrentThread();
87             }
88 
89             // Tell the first thread we are attempting a write lock so it can try to grab it's second read lock.
90             attemptingWriteLock = true;
91 
92             // Try to get the write lock
93             mutex.writeLock();
94             writeLock = true;
95             mutex.writeUnlock();
96         }
97     };
98 }
99 
100 // Disabled temporarily b/c it's breaking the Travis build for some reason.  Works fine on regular machines.
101 /*
102 TEST_CASE( "ReadWriteMutex can handle multiple read locks from the same thread while a writer is trying to lock" ) {
103 
104     ReadWriteMutexTest::Thread1 thread1;
105     ReadWriteMutexTest::Thread2 thread2;
106 
107     // Start both threads
108     thread1.start();
109     thread2.start();
110 
111     // Wait a couple of seconds for the threads to actually start.
112     OpenThreads::Thread::microSleep(2e6);
113 
114     // Let the threads go for up to 5 seconds.  If they don't finish in that amount of time they are deadlocked.
115     double maxTimeSeconds = 5.0;
116     double elapsedTime = 0.0;
117 
118     osg::Timer_t startTime = osg::Timer::instance()->tick();
119     while (thread1.isRunning() && thread2.isRunning())
120     {
121         OpenThreads::Thread::YieldCurrentThread();
122         elapsedTime = osg::Timer::instance()->delta_s(startTime, osg::Timer::instance()->tick());
123         if (elapsedTime >= maxTimeSeconds)
124         {
125             OE_NOTICE << "Threads failed to complete in " << elapsedTime << " seconds" << std::endl;
126             break;
127         }
128     }
129 
130     OE_NOTICE << "Elapsed time = " << elapsedTime << std::endl;
131     REQUIRE(!thread1.isRunning());
132     REQUIRE(!thread2.isRunning());
133     REQUIRE(elapsedTime < maxTimeSeconds);
134 }
135 */