Skip to content

Commit

Permalink
publish 5.0.0-alpha1 preview code (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly authored Jun 5, 2020
1 parent 5d022ad commit 12f9a8c
Show file tree
Hide file tree
Showing 225 changed files with 17,983 additions and 13,852 deletions.
51 changes: 29 additions & 22 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,27 @@ orbs:
workflows:
workflow:
jobs:
# Note that currently we are using a golangci-lint version that supports our oldest compatible
# version, 1.8. Because of how build tags are handled in golangci-lint, that version is not
# compatible with 1.12 and above. So, until a future version where we drop older versions, we
# won't run the linter in 1.12 and above.
- go-test:
name: Go 1.14
docker-image: circleci/golang:1.14
run-lint: false
- go-test:
name: Go 1.13
docker-image: circleci/golang:1.13
run-lint: false
- go-test:
name: Go 1.12
docker-image: circleci/golang:1.12
run-lint: false
- go-test:
name: Go 1.11
docker-image: circleci/golang:1.11
- go-test:
name: Go 1.10
docker-image: circleci/golang:1.10
- go-test:
name: Go 1.9
docker-image: circleci/golang:1.9
- go-test:
name: Go 1.8
docker-image: circleci/golang:1.8
- go-test-windows:
name: Windows
- benchmarks

jobs:
go-test:
parameters:
run-lint:
type: boolean
default: true
# We are currently linting in all Go versions, but this parameter lets us turn it off for some, in
# case we need to support more versions than a single version of golangci-lint is compatible with
docker-image:
type: string

Expand All @@ -58,7 +42,7 @@ jobs:
- image: consul
- image: amazon/dynamodb-local

working_directory: /go/src/gopkg.in/launchdarkly/go-server-sdk.v4
working_directory: /go/src/gopkg.in/launchdarkly/go-server-sdk.v5

steps:
- checkout
Expand All @@ -67,7 +51,7 @@ jobs:
condition: <<parameters.run-lint>>
steps:
- run: make lint
- run: dep ensure -dry-run
# - run: dep ensure -dry-run # we're currently using private repos for some of our dependencies, so this won't work in CI
- run: go build ./...

- run:
Expand All @@ -91,7 +75,7 @@ jobs:

environment:
GOPATH: C:\Users\VssAdministrator\go
PACKAGE_PATH: gopkg.in\launchdarkly\go-server-sdk.v4
PACKAGE_PATH: gopkg.in\launchdarkly\go-server-sdk.v5

steps:
- checkout
Expand Down Expand Up @@ -137,3 +121,26 @@ jobs:
command: |
cd ${env:GOPATH}\src\${env:PACKAGE_PATH}
go test -race ./...
benchmarks:
docker:
- image: circleci/golang:1.14
environment:
CIRCLE_ARTIFACTS: /tmp/circle-artifacts
COMMON_GO_PACKAGES: >
github.com/golang/dep/cmd/dep
working_directory: /go/src/gopkg.in/launchdarkly/go-server-sdk.v5

steps:
- checkout
- run: go get -u github.com/golang/dep/cmd/dep
- run: go build ./...
- run:
name: Run benchmarks
command: |
mkdir -p $CIRCLE_ARTIFACTS
make benchmarks | tee $CIRCLE_ARTIFACTS/benchmarks.txt
- store_artifacts:
path: /tmp/circle-artifacts
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
bin/
go-server-sdk.test
allocations.out
7 changes: 7 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ run:
linters:
enable-all: true
disable:
- funlen # allow long/complex functions for now
- gochecknoglobals
- gocognit # allow long/complex functions for now
- gocritic # very aggressive about preferring switch statements to if/else
- gocyclo
- gofmt
- gomnd # extremely aggressive linter that complains about all numeric literals, even just adding 1 to something (https://github.com/tommy-muehle/go-mnd)
- maligned
- unparam
- lll
- whitespace
- wsl # enforces a very specific style with an idiosyncratic definition of what "cuddling" is
fast: false

linter-settings:
Expand Down
7 changes: 4 additions & 3 deletions .ldrelease/update-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

set -ue

LDCLIENT_GO_TEMP=./ldclient.go.tmp
sed "s/const Version =.*/const Version = \"${LD_RELEASE_VERSION}\"/g" ldclient.go > ${LDCLIENT_GO_TEMP}
mv ${LDCLIENT_GO_TEMP} ldclient.go
SOURCE_FILE=./internal/version.go
TEMP_FILE=${SOURCE_FILE}.tmp
sed "s/const SDKVersion =.*/const SDKVersion = \"${LD_RELEASE_VERSION}\"/g" ${SOURCE_FILE} > ${TEMP_FILE}
mv ${TEMP_FILE} ${SOURCE_FILE}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"go.lintTool": "golangci-lint"
}
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Note: if you are using the LaunchDarkly Relay Proxy to forward events, update th

## [4.12.0] - 2019-09-12
### Added:
- The Go SDK now has log levels, similar to the logging frameworks used in the other LaunchDarkly SDKs. Log messages can have a level of Debug, Info, Warn, or Error; by default, Debug is hidden. The new package [`ldlog`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v4/ldlog) defines these levels, and you can use `Config.Loggers.SetMinLevel()` and `Config.Loggers.SetBaseLogger()` to control the behavior. The old property `Config.Logger` still works but is deprecated.
- The Go SDK now has log levels, similar to the logging frameworks used in the other LaunchDarkly SDKs. Log messages can have a level of Debug, Info, Warn, or Error; by default, Debug is hidden. The new package [`ldlog`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v5/ldlog) defines these levels, and you can use `Config.Loggers.SetMinLevel()` and `Config.Loggers.SetBaseLogger()` to control the behavior. The old property `Config.Logger` still works but is deprecated.
- The SDK will produce very detailed output if you call `Config.Loggers.SetMinLevel(ldlog.Debug)`. This includes information about when and how it connects to LaunchDarkly, and a full dump of all analytics event data it is sending. Since the debug logging is very verbose, and the event data includes user properties, you should not normally enable this log level in production unless advised to by LaunchDarkly support.
- There is now a different property for specifying a feature store mechanism: `Config.FeatureStoreFactory`, which takes a factory method, rather than `Config.FeatureStore`, which takes an implementation instance. Using a factory method allows the implementation to access `Config` properties such as the logging configuration. The new methods `NewInMemoryFeatureStoreFactory`, `redis.NewRedisFeatureStoreFactory`, `consul.NewConsulFeatureStoreFactory`, and `dynamodb.NewDynamoDBFeatureStoreFactory` work with this mechanism.
- The SDK's CI build now verifies compatibility with Go 1.11 and 1.12.
Expand Down Expand Up @@ -147,7 +147,7 @@ Note: if you are using the LaunchDarkly Relay Proxy to forward events, update th

## [4.7.3] - 2019-04-29
### Changed:
- Import paths in subpackages and tests have been changed from `gopkg.in/launchdarkly/go-client.v4` to `gopkg.in/launchdarkly/go-server-sdk.v4`. Users of this SDK should update their import paths accordingly.
- Import paths in subpackages and tests have been changed from `gopkg.in/launchdarkly/go-client.v4` to `gopkg.in/launchdarkly/go-server-sdk.v5`. Users of this SDK should update their import paths accordingly.
- This is the first release from the new `launchdarkly/go-server-sdk` repository.

## [4.7.2] - 2019-04-25
Expand All @@ -161,7 +161,7 @@ Note: if you are using the LaunchDarkly Relay Proxy to forward events, update th
### Note on future releases:
The LaunchDarkly SDK repositories are being renamed for consistency. All future releases of the Go SDK will use the name `go-server-sdk` rather than `go-client`. The import path will change to:

"gopkg.in/launchdarkly/go-server-sdk.v4"
"gopkg.in/launchdarkly/go-server-sdk.v5"

Since Go uses the repository name as part of the import path, to avoid breaking existing code, we will retain the existing `go-client` repository as well. However, it will not be updated after this release.

Expand All @@ -188,11 +188,11 @@ Since Go uses the repository name as part of the import path, to avoid breaking

## [4.5.0] - 2018-11-14
### Added:
- It is now possible to use DynamoDB or Consul as a persistent feature store, similar to the existing Redis integration. See the [`ldconsul`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v4/ldconsul) and [`lddynamodb`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v4/lddynamodb) subpackages, and the reference guide to ["Using a persistent feature store"](https://docs.launchdarkly.com/v2.0/docs/using-a-persistent-feature-store).
- It is now possible to use DynamoDB or Consul as a persistent feature store, similar to the existing Redis integration. See the [`ldconsul`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v5/ldconsul) and [`lddynamodb`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v5/lddynamodb) subpackages, and the reference guide to ["Using a persistent feature store"](https://docs.launchdarkly.com/v2.0/docs/using-a-persistent-feature-store).

## [4.4.0] - 2018-10-30
### Added:
- It is now possible to inject feature flags into the client from local JSON or YAML files, replacing the normal LaunchDarkly connection. This would typically be for testing purposes. See the [`ldfiledata`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v4/ldfiledata) and [`ldfilewatch`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v4/ldfilewatch) subpackages.
- It is now possible to inject feature flags into the client from local JSON or YAML files, replacing the normal LaunchDarkly connection. This would typically be for testing purposes. See the [`ldfiledata`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v5/ldfiledata) and [`ldfilewatch`](https://godoc.org/gopkg.in/launchdarkly/go-server-sdk.v5/ldfilewatch) subpackages.

- The `AllFlagsState` method now accepts a new option, `DetailsOnlyForTrackedFlags`, which reduces the size of the JSON representation of the flag state by omitting some metadata. Specifically, it omits any data that is normally used for generating detailed evaluation events if a flag does not have event tracking or debugging turned on.

Expand Down
94 changes: 84 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
Contributing to the LaunchDarkly Server-side SDK for Go
================================================
# Contributing to the LaunchDarkly Server-side SDK for Go

LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/docs/sdk-contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK.

Submitting bug reports and feature requests
------------------
## Submitting bug reports and feature requests

The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/go-server-sdk/issues) in the SDK repository. Bug reports and feature requests specific to this SDK should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days.

Submitting pull requests
------------------
## Submitting pull requests

We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days.

Build instructions
------------------
## Build instructions

### Prerequisites

The SDK should be built against Go 1.8 or newer.

Note that the SDK's public import path is `gopkg.in/launchdarkly/go-server-sdk.v4` (using the [`gopkg.in`](https://labix.org/gopkg.in) service as a simple way to pin to a major version). Since it does not use Go modules, and it references its own import path in imports between packages, this means that in order to build it you must check it out at `$GOPATH/src/gopkg.in/launchdarkly/go-server-sdk.v4`-- not `$GOPATH/src/github.com/launchdarkly/go-server-sdk`.
Note that the SDK's public import path is `gopkg.in/launchdarkly/go-server-sdk.v5` (using the [`gopkg.in`](https://labix.org/gopkg.in) service as a simple way to pin to a major version). Since it does not use Go modules, and it references its own import path in imports between packages, this means that in order to build it you must check it out at `$GOPATH/src/gopkg.in/launchdarkly/go-server-sdk.v5`-- not `$GOPATH/src/github.com/launchdarkly/go-server-sdk`.

Dependencies are managed with `dep`; after changing any imports, run `dep ensure`.

Expand Down Expand Up @@ -50,4 +46,82 @@ To build the SDK and run all unit tests:
make test
```

By default, the full unit test suite includes live tests of the integrations for Consul, DynamoDB, and Redis. Those tests expect you to have instances of all of those databases running locally. To skip them, set the environment variable `LD_SKIP_DATABASE_TESTS=1` before running the tests.
By default, the full unit test suite includes live tests of the integrations for Consul, DynamoDB, and Redis. Those tests expect you to have instances of all of those databases running locally. To skip them, set the environment variable `LD_SKIP_DATABASE_TESTS=1` before running the tests. If you do want to run them, the simplest way to provide the databases is with Docker:

```bash
docker run -p 8500:8500 consul
docker run -p 8000:8000 amazon/dynamodb-local
docker run -p 6379:6379 redis
```

## Best practices for performance

The Go SDK can be used in high-traffic application/service code where performance is critical. There are a number of coding principles to keep in mind for maximizing performance. The benchmarks that are run in CI are helpful in measuring the impact of code changes in this regard.

### Avoid heap allocations

Go's memory model uses a mix of stack and heap allocations, with the compiler transparently choosing the most appropriate strategy based on various type and scope rules. It is always preferable, when possible, to keep ephemeral values on the stack rather than on the heap to avoid creating extra work for the garbage collector.

- The most obvious rule is that anything explicitly allocated by reference (`x := &SomeType{}`), or returned by reference (`return &x`), will be allocated on the heap. Avoid this unless the object has mutable state that must be shared.
- Casting a value type to an interface causes it to be allocated on the heap, since an interface is really a combination of a type identifier and a hidden pointer.
- A closure that references any variables outside of its scope (including the method receiver, if it is inside a method) causes an object to be allocated on the heap containing the values or addresses of those variables.
- Treating a method as an anonymous function (`myFunc := someReceiver.SomeMethod`) is equivalent to a closure.

Allocations are counted in the benchmark output: "5 allocs/op" means that a total of 5 heap objects were allocated during each run of the benchmark. This does not mean that the objects were retained, only that they were allocated at some point.

For a much (MUCH) more detailed breakdown of this behavior, you may use the option `GODEBUG=allocfreetrace=1` while running a unit test or benchmark. This provides the type and code location of literally every heap allocation during the run. The output is extremely verbose, so it is recommended that you:

1. use the Makefile helper `benchmark-allocs` (see below) to reduce the number of benchmark runs and avoid capturing allocations from the Go tools themselves;
2. search the stacktrace output to find the method you are actually testing (such as `BoolVariation`) rather than the benchmark function name, so you are not looking at actions that are just part of the benchmark setup;
3. consider writing a smaller temporary benchmark specifically for this purpose, since most of the existing benchmarks will iterate over a series of parameters.

```bash
BENCHMARK=BenchmarkMySampleOperation make benchmark-allocs
```

### Avoid `defer` in simple cases

It's common to use `defer` to guarantee cleanup of some kind of temporary state when a method exits, such as releasing a lock. As convenient as this feature is, it should be avoided in high-traffic code paths _if it is safe to do so_, due to its small but consistent [runtime overhead](https://medium.com/i0exception/runtime-overhead-of-using-defer-in-go-7140d5c40e32).

It is safe to avoid `defer` if:

1. there is only one possible return point from the function, _and_:
2. there is no possibility of a panic at any point where a premature exit would leave things in an unwanted state.

Therefore, this optimization should be used only in small methods where the flow is simple and it is possible to prove that no panic can occur within the critical path.

Less preferable:

```go
func (t *thing) getNumber() int {
t.lock.Lock()
defer t.lock.Unlock()
if t.isTwo {
return 2
}
return 1
}
```

More preferable:

```go
func (t *thing) getNumber() int {
t.lock.Lock()
answer := 1
if t.isTwo {
answer = 2
}
t.lock.Unlock()
return answer
}
```

Note that in this example, a panic _is_ possible on the first line if `t` is nil; but since the lock would not get locked in that scenario, things would still be left in a safe state even in that case.

### Minimize overhead when logging at debug level

The `Debug` logging level can be a useful diagnostic tool, and it is OK to log very verbosely at this level. However, to avoid slowing things down in the usual case where this log level is disabled, keep in mind:

1. Avoid string concatenation; use `Printf`-style placeholders instead.
2. Before calling `loggers.Debug` or `loggers.Debugf`, check `loggers.IsDebugEnabled()`. If it returns false, you should skip the `Debug` or `Debugf` call, since otherwise it can incur some overhead from converting the parameters of that call to `interface{}` values.
Loading

0 comments on commit 12f9a8c

Please sign in to comment.