/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


// Thread-synchronization utility class for reproducing races in unit tests.

#ifndef PAGESPEED_KERNEL_THREAD_THREAD_SYNCHRONIZER_H__
#define PAGESPEED_KERNEL_THREAD_THREAD_SYNCHRONIZER_H__

#include <map>

#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"

namespace net_instaweb {

class AbstractMutex;
class ThreadSystem;
class Timer;

// Helps create deterministic multi-threaded tests targeting
// programmer-identified race conditions.
//
// Note that the goal of this class is not to provoke, or test for
// potential race conditions.  That is a noble goal, left for another
// day.
//
// The goal of this class is to help programmers that suspect they have
// found a race condition reproduce it robustly in a way that can be
// checked in as a unit-test and run instantly.
//
// This methodology does not rely on sleep().  It uses condition
// variables so it will run as fast as possible.  The sync-points can
// be left in all but the most nano-second-optimized production code
// as they are no-ops (single inlined if-statement) when turned off.
//
// This class is disabled by default, so that calls to it can be left in
// production code, but enabled for targeted tests.
class ThreadSynchronizer {
 public:
  explicit ThreadSynchronizer(ThreadSystem* thread_system);
  ~ThreadSynchronizer();

  // By default, the synchronizer is a no-op so we can inject sync-points
  // in production code at sensitive points with minimal overhead.  To
  // enable in a test, call EnableForPrefix("Prefix"), which will enable
  // any synchronization key beginning with "Prefix".
  //
  // EnableForPrefix should be called prior to any threading.
  void EnableForPrefix(StringPiece prefix) {
    enabled_ = true;
    prefix.CopyToString(StringVectorAdd(&prefixes_));
  }

  // Waits for a thread to signal the specified key.
  void Wait(const char* key) {
    if (enabled_) {
      DoWait(key);
    }
  }

  // Waits for a thread to signal the specified key, or the specified timeout in
  // milliseconds, whichever comes first.  Note that even if the timer expires,
  // the program should eventually Signal the key unless AllowSloppyTermination
  // is called.
  void TimedWait(const char* key, int64 timeout_ms) {
    if (enabled_) {
      DoTimedWait(key, timeout_ms);
    }
  }

  // Signals any thread waiting for a key that it can continue.  Signals
  // delivered in advance of a wait are remembered.  It is an error to
  // destruct the ThreadSynchronizer with pending signals, unless
  // AllowSloppyTermination is called for the key.
  void Signal(const char* key) {
    if (enabled_) {
      DoSignal(key);
    }
  }

  // Signals that are delivered in a timing-dependent fashion may not
  // be totally balanced at the end of a test.  Such signals should be
  // the exception rather than the rule, but they can be declared with
  // this method.  This method is intended to be called from tests
  // only, thus it does not have an inline shunt.
  void AllowSloppyTermination(const char* key);

 private:
  class SyncPoint;
  typedef std::map<GoogleString, SyncPoint*> SyncMap;

  SyncPoint* GetSyncPoint(const GoogleString& key);
  void DoWait(const char* key);
  void DoTimedWait(const char* key, int64 timeout_ms);
  void DoSignal(const char* key);
  bool MatchesPrefix(const char* key) const;

  bool enabled_;
  ThreadSystem* thread_system_;
  SyncMap sync_map_;
  scoped_ptr<AbstractMutex> map_mutex_;
  scoped_ptr<Timer> timer_;
  StringVector prefixes_;

  DISALLOW_COPY_AND_ASSIGN(ThreadSynchronizer);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_KERNEL_THREAD_THREAD_SYNCHRONIZER_H__
