mutex-test.c++ 6.12 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
21 22 23

#include "mutex.h"
#include "debug.h"
Kenton Varda's avatar
Kenton Varda committed
24
#include "thread.h"
25
#include <kj/compat/gtest.h>
26
#include <stdlib.h>
27

28
#if _WIN32
29
#define NOGDI  // NOGDI is needed to make EXPECT_EQ(123u, *lock) compile for some reason
30
#include <windows.h>
31
#undef NOGDI
32 33
#else
#include <pthread.h>
34
#include <unistd.h>
35 36
#endif

37 38 39
namespace kj {
namespace {

40 41 42
#if _WIN32
inline void delay() { Sleep(10); }
#else
43
inline void delay() { usleep(10000); }
44
#endif
45 46 47 48 49

TEST(Mutex, MutexGuarded) {
  MutexGuarded<uint> value(123);

  {
50
    Locked<uint> lock = value.lockExclusive();
Kenton Varda's avatar
Kenton Varda committed
51
    EXPECT_EQ(123u, *lock);
52
    EXPECT_EQ(123u, value.getAlreadyLockedExclusive());
53 54

    Thread thread([&]() {
55
      Locked<uint> threadLock = value.lockExclusive();
Kenton Varda's avatar
Kenton Varda committed
56
      EXPECT_EQ(456u, *threadLock);
57 58 59 60
      *threadLock = 789;
    });

    delay();
Kenton Varda's avatar
Kenton Varda committed
61
    EXPECT_EQ(123u, *lock);
62 63 64 65
    *lock = 456;
    auto earlyRelease = kj::mv(lock);
  }

Kenton Varda's avatar
Kenton Varda committed
66
  EXPECT_EQ(789u, *value.lockExclusive());
67 68

  {
69
    auto rlock1 = value.lockShared();
70 71
    EXPECT_EQ(789u, *rlock1);
    EXPECT_EQ(789u, value.getAlreadyLockedShared());
72

73 74
    {
      auto rlock2 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
75
      EXPECT_EQ(789u, *rlock2);
76
      auto rlock3 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
77
      EXPECT_EQ(789u, *rlock3);
78
      auto rlock4 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
79
      EXPECT_EQ(789u, *rlock4);
80 81
    }

82
    Thread thread2([&]() {
83
      Locked<uint> threadLock = value.lockExclusive();
84 85 86
      *threadLock = 321;
    });

87 88 89 90 91 92 93 94
#if KJ_USE_FUTEX
    // So, it turns out that pthread_rwlock on BSD "prioritizes" readers over writers.  The result
    // is that if one thread tries to take multiple read locks, but another thread happens to
    // request a write lock it between, you get a deadlock.  This seems to contradict the man pages
    // and common sense, but this is how it is.  The futex-based implementation doesn't currently
    // have this problem because it does not prioritize writers.  Perhaps it will in the future,
    // but we'll leave this test here until then to make sure we notice the change.

95
    delay();
Kenton Varda's avatar
Kenton Varda committed
96
    EXPECT_EQ(789u, *rlock1);
97 98

    {
99
      auto rlock2 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
100
      EXPECT_EQ(789u, *rlock2);
101
      auto rlock3 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
102
      EXPECT_EQ(789u, *rlock3);
103
      auto rlock4 = value.lockShared();
Kenton Varda's avatar
Kenton Varda committed
104
      EXPECT_EQ(789u, *rlock4);
105
    }
106
#endif
107 108

    delay();
Kenton Varda's avatar
Kenton Varda committed
109
    EXPECT_EQ(789u, *rlock1);
110 111 112
    auto earlyRelease = kj::mv(rlock1);
  }

Kenton Varda's avatar
Kenton Varda committed
113
  EXPECT_EQ(321u, *value.lockExclusive());
114

115
#if !_WIN32  // Not checked on win32.
116 117
  EXPECT_DEBUG_ANY_THROW(value.getAlreadyLockedExclusive());
  EXPECT_DEBUG_ANY_THROW(value.getAlreadyLockedShared());
118
#endif
119
  EXPECT_EQ(321u, value.getWithoutLock());
120 121
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
#if KJ_USE_FUTEX    // TODO(soon): Implement on pthread & win32
TEST(Mutex, When) {
  MutexGuarded<uint> value(123);

  {
    uint m = value.when([](uint n) { return n < 200; }, [](uint& n) {
      ++n;
      return n + 2;
    });
    KJ_EXPECT(m == 126);

    KJ_EXPECT(*value.lockShared() == 124);
  }

  {
    kj::Thread thread([&]() {
      delay();
      *value.lockExclusive() = 321;
    });

    uint m = value.when([](uint n) { return n > 200; }, [](uint& n) {
      ++n;
      return n + 2;
    });
    KJ_EXPECT(m == 324);

    KJ_EXPECT(*value.lockShared() == 322);
  }

  {
    // Stress test. 100 threads each wait for a value and then set the next value.
    *value.lockExclusive() = 0;

    auto threads = kj::heapArrayBuilder<kj::Own<kj::Thread>>(100);
    for (auto i: kj::zeroTo(100)) {
      threads.add(kj::heap<kj::Thread>([i,&value]() {
        if (i % 2 == 0) delay();
        uint m = value.when([i](const uint& n) { return n == i; },
160
            [](uint& n) { return n++; });
161 162 163 164 165 166 167 168 169 170 171 172 173 174
        KJ_ASSERT(m == i);
      }));
    }

    uint m = value.when([](uint n) { return n == 100; }, [](uint& n) {
      return n++;
    });
    KJ_EXPECT(m == 100);

    KJ_EXPECT(*value.lockShared() == 101);
  }
}
#endif

175 176
TEST(Mutex, Lazy) {
  Lazy<uint> lazy;
177
  volatile bool initStarted = false;
178 179

  Thread thread([&]() {
Kenton Varda's avatar
Kenton Varda committed
180
    EXPECT_EQ(123u, lazy.get([&](SpaceFor<uint>& space) -> Own<uint> {
181
      initStarted = true;
182 183 184 185 186 187
      delay();
      return space.construct(123);
    }));
  });

  // Spin until the initializer has been entered in the thread.
188
  while (!initStarted) {
189 190 191
#if _WIN32
    Sleep(0);
#else
192
    sched_yield();
193
#endif
194 195
  }

Kenton Varda's avatar
Kenton Varda committed
196 197
  EXPECT_EQ(123u, lazy.get([](SpaceFor<uint>& space) { return space.construct(456); }));
  EXPECT_EQ(123u, lazy.get([](SpaceFor<uint>& space) { return space.construct(789); }));
198 199
}

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
TEST(Mutex, LazyException) {
  Lazy<uint> lazy;

  auto exception = kj::runCatchingExceptions([&]() {
    lazy.get([&](SpaceFor<uint>& space) -> Own<uint> {
          KJ_FAIL_ASSERT("foo") { break; }
          return space.construct(123);
        });
  });
  EXPECT_TRUE(exception != nullptr);

  uint i = lazy.get([&](SpaceFor<uint>& space) -> Own<uint> {
        return space.construct(456);
      });

215 216 217 218 219
  // Unfortunately, the results differ depending on whether exceptions are enabled.
  // TODO(someday):  Fix this?  Does it matter?
#if KJ_NO_EXCEPTIONS
  EXPECT_EQ(123, i);
#else
220
  EXPECT_EQ(456, i);
221
#endif
222 223
}

224 225
}  // namespace
}  // namespace kj