pax_global_header00006660000000000000000000000064151607626010014516gustar00rootroot0000000000000052 comment=9b322960ec63dda0c205b937d69b314ddf18fca6 golang-github-imkira-go-observer-1.0.3/000077500000000000000000000000001516076260100177505ustar00rootroot00000000000000golang-github-imkira-go-observer-1.0.3/.codebeatignore000066400000000000000000000000131516076260100227150ustar00rootroot00000000000000examples/* golang-github-imkira-go-observer-1.0.3/.gitignore000066400000000000000000000000301516076260100217310ustar00rootroot00000000000000.DS_Store /coverage.txt golang-github-imkira-go-observer-1.0.3/.travis.yml000066400000000000000000000004731516076260100220650ustar00rootroot00000000000000language: go go: - 1.2 - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 - tip before_install: if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then make deps; fi script: - if [[ $TRAVIS_GO_VERSION == 1.7* ]]; then make gometalinter; fi - make test - make cover after_success: - bash <(curl -s https://codecov.io/bash) golang-github-imkira-go-observer-1.0.3/LICENSE.txt000066400000000000000000000020641516076260100215750ustar00rootroot00000000000000Copyright (c) 2014 Mario Freitas (imkira@gmail.com) 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. golang-github-imkira-go-observer-1.0.3/Makefile000066400000000000000000000007011516076260100214060ustar00rootroot00000000000000.PHONY: all deps gometalinter test cover all: gometalinter test cover deps: go get -u github.com/alecthomas/gometalinter gometalinter --install gometalinter: gometalinter --vendor --deadline=1m --tests \ --enable=gofmt \ --enable=goimports \ --enable=lll \ --enable=misspell \ --enable=unused test: go test -v -race -cpu=1,2,4 -coverprofile=coverage.txt -covermode=atomic cover: go tool cover -html=coverage.txt -o coverage.html golang-github-imkira-go-observer-1.0.3/README.md000066400000000000000000000117361516076260100212370ustar00rootroot00000000000000# observer [![License](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://github.com/imkira/go-observer/blob/master/LICENSE.txt) [![GoDoc](https://godoc.org/github.com/imkira/go-observer?status.svg)](https://godoc.org/github.com/imkira/go-observer) [![Build Status](http://img.shields.io/travis/imkira/go-observer.svg?style=flat)](https://travis-ci.org/imkira/go-observer) [![Coverage](https://codecov.io/gh/imkira/go-observer/branch/master/graph/badge.svg)](https://codecov.io/gh/imkira/go-observer) [![codebeat badge](https://codebeat.co/badges/28bdd579-8b34-4940-a3e0-35ac52794a42)](https://codebeat.co/projects/github-com-imkira-go-observer) [![goreportcard](https://goreportcard.com/badge/github.com/imkira/go-observer)](https://goreportcard.com/report/github.com/imkira/go-observer) observer is a [Go](http://golang.org) package that aims to simplify the problem of channel-based broadcasting of events from one or more publishers to one or more observers. # Problem The typical quick-and-dirty approach to notifying a set of observers in go is to use channels and call each in a for loop, like the following: ```go for _, channel := range channels { channel <- value } ``` There are two problems with this approach: - The broadcaster blocks every time some channel is not ready to be written to. - If the broadcaster blocks for some channel, the remaining channels will not be written to (and therefore not receive the event) until the blocking channel is finally ready. - It is O(N). The more observers you have, the worse this loop will behave. Of course, this could be solved by creating one goroutine for each channel so the broadcaster doesn't block. Unfortunately, this is heavy and resource-consuming. This is especially bad if you have events being raised frequently and a considerable number of observers. # Approach The way observer package tackles this problem is very simple. For every event, a state object containing information about the event, and a channel is created. State objects are managed using a singly linked list structure: every state points to the next. When a new event is raised, a new state object is appended to the list and the channel of the previous state is closed (this helps notify all observers that the previous state is outdated). Package observer defines 2 concepts: - Property: An object that is continuously updated by one or more publishers. - Stream: The list of values a property is updated to. For every property update, that value is appended to the list in the order they happen, and is only discarded when you advance to the next value. # Memory Usage The amount of memory used for one property is not dependent on the number of observers. It should be proportional to the number of value updates since the value last obtained by the slowest observer. As long as you keep advancing all your observers, garbage collection will take place and keep memory usage stable. # How to Use First, you need to install the package: ``` go get -u github.com/imkira/go-observer ``` Then, you need to include it in your source: ```go import "github.com/imkira/go-observer" ``` The package will be imported with ```observer``` as name. The following example creates one property that is updated every second by one or more publishers, and observed by one or more observers. ## Documentation For advanced usage, make sure to check the [available documentation here](http://godoc.org/github.com/imkira/go-observer). ## Example: Creating a Property The following code creates a property with initial value ```1```. ```go val := 1 prop := observer.NewProperty(val) ``` After creating the property, you can pass it around to publishers or observers as you want. ## Example: Publisher The following code represents a publisher that increments the value of the property by one every second. ```go val := 1 for { time.Sleep(time.Second) val += 1 fmt.Printf("will publish value: %d\n", val) prop.Update(val) } ``` Note: - Property is goroutine safe: you can use it concurrently from multiple goroutines. ## Example: Observer The following code represents an observer that prints the initial value of a property and waits indefinitely for changes to its value. When there is a change, the stream is advanced and the current value of the property is printed. ```go stream := prop.Observe() val := stream.Value().(int) fmt.Printf("initial value: %d\n", val) for { select { // wait for changes case <-stream.Changes(): // advance to next value stream.Next() // new value val = stream.Value().(int) fmt.Printf("got new value: %d\n", val) } } ``` Note: - Stream is not goroutine safe: You must create one stream by calling ```Property.Observe()``` or ```Stream.Clone()``` if you want to have concurrent observers for the same property or stream. ## Example Please check [examples/multiple.go](https://github.com/imkira/go-observer/blob/master/examples/multiple.go) for a simple example on how to use multiple observers with a single updater. golang-github-imkira-go-observer-1.0.3/doc.go000066400000000000000000000002431516076260100210430ustar00rootroot00000000000000// Package observer aims to simplify the problem of channel-based broadcasting // of events from one or more publishers to one or more observers. package observer golang-github-imkira-go-observer-1.0.3/examples/000077500000000000000000000000001516076260100215665ustar00rootroot00000000000000golang-github-imkira-go-observer-1.0.3/examples/multiple.go000066400000000000000000000014371516076260100237550ustar00rootroot00000000000000package main import ( "fmt" "time" "github.com/imkira/go-observer" ) func runPublisher(prop observer.Property) { val := prop.Value().(int) for { time.Sleep(time.Second) // update property val++ prop.Update(val) } } func runObserver(id int, prop observer.Property) { stream := prop.Observe() for { val := stream.Value().(int) fmt.Printf("Observer: %d, Value: %d\n", id, val) select { // wait for changes case <-stream.Changes(): // advance to next value stream.Next() } } } func main() { // create a property with initial value prop := observer.NewProperty(1) // run 10 observers for i := 0; i < 10; i++ { go runObserver(i, prop) } // run one publisher go runPublisher(prop) // terminate program after 10 seconds time.Sleep(10 * time.Second) } golang-github-imkira-go-observer-1.0.3/property.go000066400000000000000000000020111516076260100221550ustar00rootroot00000000000000package observer import "sync" // Property is an object that is continuously updated by one or more // publishers. It is completely goroutine safe: you can use Property // concurrently from multiple goroutines. type Property interface { // Value returns the current value for this property. Value() interface{} // Update sets a new value for this property. Update(value interface{}) // Observe returns a newly created Stream for this property. Observe() Stream } // NewProperty creates a new Property with the initial value value. // It returns the created Property. func NewProperty(value interface{}) Property { return &property{state: newState(value)} } type property struct { sync.RWMutex state *state } func (p *property) Value() interface{} { p.RLock() defer p.RUnlock() return p.state.value } func (p *property) Update(value interface{}) { p.Lock() defer p.Unlock() p.state = p.state.update(value) } func (p *property) Observe() Stream { p.RLock() defer p.RUnlock() return &stream{state: p.state} } golang-github-imkira-go-observer-1.0.3/property_test.go000066400000000000000000000040331516076260100232220ustar00rootroot00000000000000package observer import ( "sync" "testing" ) func TestPropertyInitialValue(t *testing.T) { prop := NewProperty(10) if val := prop.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } stream := prop.Observe() for i := 0; i <= 100; i++ { if val := stream.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } } } func TestPropertyInitialObserve(t *testing.T) { prop := NewProperty(10) var prevStream Stream for i := 0; i <= 100; i++ { stream := prop.Observe() if stream == prevStream { t.Fatalf("Expecting different stream\n") } if val := stream.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } prevStream = stream } } func TestPropertyObserveAfterUpdate(t *testing.T) { prop := NewProperty(10) if val := prop.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } prop.Update(15) if val := prop.Value(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } stream := prop.Observe() if val := stream.Value(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } } func TestPropertyMultipleConcurrentReaders(t *testing.T) { initial := 1000 final := 2000 prop := NewProperty(initial) var cherrs []chan error for i := 0; i < 1000; i++ { cherr := make(chan error, 1) cherrs = append(cherrs, cherr) go testStreamRead(prop.Observe(), initial, final, cherr) } done := make(chan bool) go func(prop Property, initial, final int, done chan bool) { defer close(done) for i := initial + 1; i <= final; i++ { prop.Update(i) } }(prop, initial, final, done) for _, cherr := range cherrs { if err := <-cherr; err != nil { t.Fatal(err) } } <-done } func TestPropertyMultipleConcurrentReadersWriters(t *testing.T) { wg := &sync.WaitGroup{} writer := func(prop Property, times int) { defer wg.Done() for i := 0; i <= times; i++ { val := prop.Value().(int) prop.Update(val + 1) prop.Observe() } } prop := NewProperty(0) times := 1000 for i := 0; i < 1000; i++ { wg.Add(1) go writer(prop, times) } wg.Wait() } golang-github-imkira-go-observer-1.0.3/state.go000066400000000000000000000004721516076260100214220ustar00rootroot00000000000000package observer type state struct { value interface{} next *state done chan struct{} } func newState(value interface{}) *state { return &state{ value: value, done: make(chan struct{}), } } func (s *state) update(value interface{}) *state { s.next = newState(value) close(s.done) return s.next } golang-github-imkira-go-observer-1.0.3/state_test.go000066400000000000000000000015531516076260100224620ustar00rootroot00000000000000package observer import "testing" func testStateNew(t *testing.T, state *state, val interface{}) { if state.value != val { t.Fatalf("Expecting %#v but got %#v\n", val, state.value) } if state.next != nil { t.Fatalf("Expecting no next but got %#v\n", state.next) } select { case <-state.done: t.Fatalf("Expecting not done\n") default: } } func TestStateNew(t *testing.T) { state := newState(10) testStateNew(t, state, 10) } func TestStateUpdate(t *testing.T) { state1 := newState(10) state2 := state1.update(15) if state1 == state2 { t.Fatalf("Expecting different states\n") } if state2.value != 15 { t.Fatalf("Expecting 15 but got %#v\n", state1.value) } if state1.next == nil { t.Fatalf("Expecting next but got %#v\n", state1.next) } select { case <-state1.done: default: t.Fatalf("Expecting done\n") } testStateNew(t, state2, 15) } golang-github-imkira-go-observer-1.0.3/stream.go000066400000000000000000000035171516076260100216000ustar00rootroot00000000000000package observer // Stream represents the list of values a property is updated to. For every // property update, that value is appended to the list in the order they // happen. The value is discarded once you advance the stream. Please note // that Stream is not goroutine safe: you cannot use the same stream on // multiple goroutines concurrently. If you want to use multiple streams for // the same property, either use Property.Observe (goroutine-safe) or use // Stream.Clone (before passing it to another goroutine). type Stream interface { // Value returns the current value for this stream. Value() interface{} // Changes returns the channel that is closed when a new value is available. Changes() chan struct{} // Next advances this stream to the next state. // You should never call this unless Changes channel is closed. Next() interface{} // HasNext checks whether there is a new value available. HasNext() bool // WaitNext waits for Changes to be closed, advances the stream and returns // the current value. WaitNext() interface{} // Clone creates a new independent stream from this one but sharing the same // Property. Updates to the property will be reflected in both streams but // they may have different values depending on when they advance the stream // with Next. Clone() Stream } type stream struct { state *state } func (s *stream) Clone() Stream { return &stream{state: s.state} } func (s *stream) Value() interface{} { return s.state.value } func (s *stream) Changes() chan struct{} { return s.state.done } func (s *stream) Next() interface{} { s.state = s.state.next return s.state.value } func (s *stream) HasNext() bool { select { case <-s.state.done: return true default: return false } } func (s *stream) WaitNext() interface{} { <-s.state.done s.state = s.state.next return s.state.value } golang-github-imkira-go-observer-1.0.3/stream_test.go000066400000000000000000000077331516076260100226430ustar00rootroot00000000000000package observer import ( "fmt" "testing" "time" ) func TestStreamInitialValue(t *testing.T) { state := newState(10) stream := &stream{state: state} if val := stream.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } } func TestStreamUpdate(t *testing.T) { state1 := newState(10) state2 := state1.update(15) stream := &stream{state: state1} if val := stream.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } state2.update(15) if val := stream.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } } func TestStreamNextValue(t *testing.T) { state1 := newState(10) stream := &stream{state: state1} state2 := state1.update(15) if val := stream.Next(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } state2.update(20) if val := stream.Next(); val != 20 { t.Fatalf("Expecting 20 but got %#v\n", val) } } func TestStreamDetectsChanges(t *testing.T) { state := newState(10) stream := &stream{state: state} select { case <-stream.Changes(): t.Fatalf("Expecting no changes\n") default: } go func() { time.Sleep(1 * time.Second) state.update(15) }() select { case <-stream.Changes(): case <-time.After(2 * time.Second): t.Fatalf("Expecting changes\n") } select { case <-stream.Changes(): default: t.Fatalf("Expecting changes\n") } if val := stream.Next(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } select { case <-stream.Changes(): t.Fatalf("Expecting no changes\n") default: } } func TestStreamHasChanges(t *testing.T) { state := newState(10) stream := &stream{state: state} if stream.HasNext() { t.Fatalf("Expecting no changes\n") } state.update(15) if !stream.HasNext() { t.Fatalf("Expecting changes\n") } if val := stream.Next(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } } func TestStreamWaitsNext(t *testing.T) { state := newState(10) stream := &stream{state: state} for i := 15; i <= 100; i++ { state = state.update(i) if val := stream.WaitNext(); val != i { t.Fatalf("Expecting %#v but got %#v\n", i, val) } } if stream.HasNext() { t.Fatalf("Expecting no changes\n") } } func TestStreamClone(t *testing.T) { state := newState(10) stream1 := &stream{state: state} stream2 := stream1.Clone() if stream2.HasNext() { t.Fatalf("Expecting no changes\n") } if val := stream2.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } state.update(15) if !stream1.HasNext() { t.Fatalf("Expecting changes\n") } if !stream2.HasNext() { t.Fatalf("Expecting changes\n") } stream1.Next() if val := stream1.Value(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } if val := stream2.Value(); val != 10 { t.Fatalf("Expecting 10 but got %#v\n", val) } stream2.Next() if val := stream2.Value(); val != 15 { t.Fatalf("Expecting 15 but got %#v\n", val) } if stream1.HasNext() { t.Fatalf("Expecting no changes\n") } if stream2.HasNext() { t.Fatalf("Expecting no changes\n") } } func TestStreamConcurrencyWithClones(t *testing.T) { initial := 1000 final := 2000 prop := NewProperty(initial) stream := prop.Observe() var cherrs []chan error for i := 0; i < 1000; i++ { cherr := make(chan error, 1) cherrs = append(cherrs, cherr) go testStreamRead(stream.Clone(), initial, final, cherr) } done := make(chan bool) go func(prop Property, initial, final int, done chan bool) { defer close(done) for i := initial + 1; i <= final; i++ { prop.Update(i) } }(prop, initial, final, done) for _, cherr := range cherrs { if err := <-cherr; err != nil { t.Fatal(err) } } <-done } func testStreamRead(s Stream, initial, final int, err chan error) { val := s.Value().(int) if val != initial { err <- fmt.Errorf("Expecting %#v but got %#v\n", initial, val) return } for i := initial + 1; i <= final; i++ { prevVal := val val = s.WaitNext().(int) expected := prevVal + 1 if val != expected { err <- fmt.Errorf("Expecting %#v but got %#v\n", expected, val) return } } close(err) }