Skip to content

Instantly share code, notes, and snippets.

@arrieta
Created September 19, 2018 14:57
Show Gist options
  • Save arrieta/3eb1eeca3cb212e6e77569fdf33fea6d to your computer and use it in GitHub Desktop.
Save arrieta/3eb1eeca3cb212e6e77569fdf33fea6d to your computer and use it in GitHub Desktop.
C++ Translation of Go Code
// I am interested in practical comparisons between C++ and Go. To this end, I
// find "good" Go code (by some definition of "good") and translate it into C++.
//
// It is mostly unimportant what the code does (in fact, it does not do anything
// at all). What matters is to attempt to capture the same API and semantics.
//
// This file implements a class called "Awareness" found in the hashicorp repo:
//
// https://github.com/hashicorp/memberlist/blob/master/awareness.go
//
#include <mutex>
#include <shared_mutex>
#include <string>
#include <vector>
// Some function that came from another Go module.
extern void SetGauge(const std::vector<std::string>&, int);
class Awareness {
public:
explicit Awareness(int max) : m_score(0), m_max(max) {}
void ApplyDelta(int delta) {
int initial_score = 0;
int final_score = 0;
{
// A unique lock is exclusive; only one thread can acquire it (normally
// used for writing).
std::unique_lock<std::shared_mutex> lock(m_rwmux);
initial_score = m_score;
m_score += delta;
if (m_score < 0) {
m_score = 0;
} else if (m_score > m_max - 1) {
m_score = m_max - 1;
}
final_score = m_score;
} // at this point the unique lock has been released
// This call is placed outside the unique lock.
if (initial_score != final_score) {
SetGauge({"memberlist", "health", "score"}, final_score);
}
}
int GetHealthScore() const {
// A shared lock is non-exclusive; several threads can simultaneously
// acquire it (normally used for reading).
std::shared_lock<std::shared_mutex> lock(m_rwmux);
return m_score;
}
int ScaleTimeout(int timeout) const {
auto score = GetHealthScore(); // thread-safe access
return timeout * (score + 1);
}
private:
int m_score;
int m_max;
// Serialize R/W access
mutable std::shared_mutex m_rwmux;
};
@arrieta
Copy link
Author

arrieta commented Sep 19, 2018

For reference, this is the original Go code:

package memberlist

import (
	"sync"
	"time"

	"github.com/armon/go-metrics"
)

// awareness manages a simple metric for tracking the estimated health of the
// local node. Health is primary the node's ability to respond in the soft
// real-time manner required for correct health checking of other nodes in the
// cluster.
type awareness struct {
	sync.RWMutex

	// max is the upper threshold for the timeout scale (the score will be
	// constrained to be from 0 <= score < max).
	max int

	// score is the current awareness score. Lower values are healthier and
	// zero is the minimum value.
	score int
}

// newAwareness returns a new awareness object.
func newAwareness(max int) *awareness {
	return &awareness{
		max:   max,
		score: 0,
	}
}

// ApplyDelta takes the given delta and applies it to the score in a thread-safe
// manner. It also enforces a floor of zero and a max of max, so deltas may not
// change the overall score if it's railed at one of the extremes.
func (a *awareness) ApplyDelta(delta int) {
	a.Lock()
	initial := a.score
	a.score += delta
	if a.score < 0 {
		a.score = 0
	} else if a.score > (a.max - 1) {
		a.score = (a.max - 1)
	}
	final := a.score
	a.Unlock()

	if initial != final {
		metrics.SetGauge([]string{"memberlist", "health", "score"}, float32(final))
	}
}

// GetHealthScore returns the raw health score.
func (a *awareness) GetHealthScore() int {
	a.RLock()
	score := a.score
	a.RUnlock()
	return score
}

// ScaleTimeout takes the given duration and scales it based on the current
// score. Less healthyness will lead to longer timeouts.
func (a *awareness) ScaleTimeout(timeout time.Duration) time.Duration {
	a.RLock()
	score := a.score
	a.RUnlock()
	return timeout * (time.Duration(score) + 1)
}

@arrieta
Copy link
Author

arrieta commented Sep 19, 2018

// In practice, I would split the interface from the implementation.  This would
// be the header file "awareness.hpp".

#pragma once

#include <shared_mutex>

class Awareness {
 public:
  explicit Awareness(int max);
  void ApplyDelta(int delta);
  int  GetHealthScore() const;
  int  ScaleTimeout(int timeout) const;

 private:
  int m_score;
  int m_max;

  // Serialize R/W access
  mutable std::shared_mutex m_rwmux;
};

// This would be the implementation file "awareness.cpp"

#include "awareness.hpp"

#include <mutex> // for std::unique_lock

#include "metrics.hpp"  // for SetGauge

Awareness::Awareness(int max) : m_score(0), m_max(max) {}

void Awareness::ApplyDelta(int delta) {
  int initial_score = 0;
  int final_score   = 0;
  {
    // A unique lock is exclusive; only one thread can acquire it (normally
    // used for writing).
    std::unique_lock<std::shared_mutex> lock(m_rwmux);

    initial_score = m_score;

    m_score += delta;
    if (m_score < 0) {
      m_score = 0;
    } else if (m_score > m_max - 1) {
      m_score = m_max - 1;
    }

    final_score = m_score;
  }  // at this point the unique lock has been released

  // This call is placed outside the unique lock.
  if (initial_score != final_score) {
    metrics::SetGauge({"memberlist", "health", "score"}, final_score);
  }
}

int Awareness::GetHealthScore() const {
  // A shared lock is non-exclusive; several threads can simultaneously
  // acquire it (normally used for reading).
  std::shared_lock<std::shared_mutex> lock(m_rwmux);

  return m_score;
}

int Awareness::ScaleTimeout(int timeout) const {
  auto score = GetHealthScore();  // thread-safe access

  return timeout * (score + 1);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment