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  }