github.com/nakagami/firebirdsql@v0.9.10/event.go (about) 1 //go:build !plan9 2 // +build !plan9 3 4 /******************************************************************************* 5 The MIT License (MIT) 6 7 Copyright (c) 2019 Arteev Aleksey 8 9 Permission is hereby granted, free of charge, to any person obtaining a copy of 10 this software and associated documentation files (the "Software"), to deal in 11 the Software without restriction, including without limitation the rights to 12 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 13 the Software, and to permit persons to whom the Software is furnished to do so, 14 subject to the following conditions: 15 16 The above copyright notice and this permission notice shall be included in all 17 copies or substantial portions of the Software. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 21 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 22 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 23 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 *******************************************************************************/ 26 27 package firebirdsql 28 29 import ( 30 "database/sql" 31 "errors" 32 "fmt" 33 "sync" 34 "sync/atomic" 35 ) 36 37 // Errors 38 var ( 39 ErrAlreadySubscribe = errors.New("already subscribe") 40 ErrFbEventClosed = errors.New("fbevent already closed") 41 ) 42 43 // SQLs 44 const ( 45 sqlPostEvent = `execute block as begin post_event '%s'; end` 46 ) 47 48 // FbEvent allows you to subscribe to events, also stores subscribers. 49 // It is possible to send events to the database. 50 type FbEvent struct { 51 mu sync.RWMutex 52 dsn *firebirdDsn 53 conn *sql.DB 54 done chan struct{} 55 closed int32 56 closer sync.Once 57 chDoneSubscriber chan *Subscription 58 subscribers []*Subscription 59 } 60 61 // Event stores event data: the amount since the last time the event was received and id 62 type Event struct { 63 Name string 64 Count int 65 ID int32 66 RemoteID int32 67 } 68 69 // EventHandler callback function type 70 type EventHandler func(e Event) 71 72 // NewFBEvent returns FbEvent for event subscription 73 func NewFBEvent(dsns string) (*FbEvent, error) { 74 conn, err := sql.Open("firebirdsql", dsns) 75 if err != nil { 76 return nil, err 77 } 78 // can ignore error, would have been thrown by sql.Open 79 dsn, _ := parseDSN(dsns) 80 fbEvent := &FbEvent{ 81 dsn: dsn, 82 conn: conn, 83 done: make(chan struct{}), 84 chDoneSubscriber: make(chan *Subscription), 85 } 86 go fbEvent.run() 87 return fbEvent, nil 88 } 89 90 // PostEvent posts an event to the database 91 func (e *FbEvent) PostEvent(name string) error { 92 _, err := e.conn.Exec(fmt.Sprintf(sqlPostEvent, name)) 93 if err != nil { 94 return err 95 } 96 return nil 97 } 98 99 func (e *FbEvent) newSubscriber(events []string, cb EventHandler, chEvent chan Event) (*Subscription, error) { 100 subscriber, err := newSubscription(e.dsn, events, cb, chEvent, e.chDoneSubscriber) 101 if err != nil { 102 return nil, err 103 } 104 e.mu.Lock() 105 defer e.mu.Unlock() 106 e.subscribers = append(e.subscribers, subscriber) 107 return subscriber, nil 108 } 109 110 // Subscribers returns slice of all subscribers 111 func (e *FbEvent) Subscribers() []*Subscription { 112 e.mu.RLock() 113 defer e.mu.RUnlock() 114 return e.subscribers[:] 115 } 116 117 // Count returns the number of subscribers 118 func (e *FbEvent) Count() int { 119 e.mu.RLock() 120 defer e.mu.RUnlock() 121 return len(e.subscribers) 122 } 123 124 // Subscribe subscribe to events using the callback function 125 func (e *FbEvent) Subscribe(events []string, cb EventHandler) (*Subscription, error) { 126 return e.newSubscriber(events, cb, nil) 127 } 128 129 // SubscribeChan subscribe to events using the channel 130 func (e *FbEvent) SubscribeChan(events []string, chEvent chan Event) (*Subscription, error) { 131 return e.newSubscriber(events, nil, chEvent) 132 } 133 134 func (e *FbEvent) run() { 135 for { 136 select { 137 case <-e.done: 138 return 139 case subscriber := <-e.chDoneSubscriber: 140 e.shutdownSubscriber(subscriber) 141 } 142 } 143 } 144 145 func (e *FbEvent) shutdownSubscriber(subscriber *Subscription) { 146 e.mu.Lock() 147 defer e.mu.Unlock() 148 for i := range e.subscribers { 149 if e.subscribers[i] == subscriber { 150 last := len(e.subscribers) - 1 151 e.subscribers[i] = e.subscribers[last] 152 e.subscribers[last] = nil 153 e.subscribers = e.subscribers[:last] 154 return 155 } 156 } 157 } 158 159 // IsClosed returns a close flag 160 func (e *FbEvent) IsClosed() bool { 161 return atomic.LoadInt32(&e.closed) == 1 162 } 163 164 // Close closes FbEvent and all subscribers 165 func (e *FbEvent) Close() error { 166 if e.IsClosed() { 167 return ErrFbEventClosed 168 } 169 return e.doClose(nil) 170 } 171 172 func (e *FbEvent) closeWithError(err error) error { 173 if e.IsClosed() { 174 return ErrFbEventClosed 175 } 176 return e.doClose(err) 177 } 178 179 func (e *FbEvent) doClose(err error) (errResult error) { 180 atomic.StoreInt32(&e.closed, 1) 181 e.closer.Do(func() { 182 e.conn.Close() 183 e.mu.Lock() 184 wg := &sync.WaitGroup{} 185 wg.Add(len(e.subscribers)) 186 for i := range e.subscribers { 187 go func(subscriber *Subscription) { 188 defer wg.Done() 189 subscriber.unsubscribeNoNotify() 190 }(e.subscribers[i]) 191 } 192 e.subscribers = make([]*Subscription, 0) 193 e.mu.Unlock() 194 wg.Wait() 195 close(e.done) 196 }) 197 return 198 }