You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

channelmanager.go 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "sync"
  6. )
  7. type channelManagerEntry struct {
  8. channel *Channel
  9. // this is a refcount for joins, so we can avoid a race where we incorrectly
  10. // think the channel is empty (without holding a lock across the entire Channel.Join()
  11. // call)
  12. pendingJoins int
  13. }
  14. // ChannelManager keeps track of all the channels on the server,
  15. // providing synchronization for creation of new channels on first join,
  16. // cleanup of empty channels on last part, and renames.
  17. type ChannelManager struct {
  18. sync.RWMutex // tier 2
  19. chans map[string]*channelManagerEntry
  20. }
  21. // NewChannelManager returns a new ChannelManager.
  22. func NewChannelManager() *ChannelManager {
  23. return &ChannelManager{
  24. chans: make(map[string]*channelManagerEntry),
  25. }
  26. }
  27. // Get returns an existing channel with name equivalent to `name`, or nil
  28. func (cm *ChannelManager) Get(name string) *Channel {
  29. name, err := CasefoldChannel(name)
  30. if err == nil {
  31. cm.RLock()
  32. defer cm.RUnlock()
  33. entry := cm.chans[name]
  34. if entry != nil {
  35. return entry.channel
  36. }
  37. }
  38. return nil
  39. }
  40. // Join causes `client` to join the channel named `name`, creating it if necessary.
  41. func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
  42. server := client.server
  43. casefoldedName, err := CasefoldChannel(name)
  44. if err != nil || len(casefoldedName) > server.Limits().ChannelLen {
  45. return errNoSuchChannel
  46. }
  47. cm.Lock()
  48. entry := cm.chans[casefoldedName]
  49. if entry == nil {
  50. // XXX give up the lock to check for a registration, then check again
  51. // to see if we need to create the channel. we could solve this by doing LoadChannel
  52. // outside the lock initially on every join, so this is best thought of as an
  53. // optimization to avoid that.
  54. cm.Unlock()
  55. info := client.server.channelRegistry.LoadChannel(casefoldedName)
  56. cm.Lock()
  57. entry = cm.chans[casefoldedName]
  58. if entry == nil {
  59. entry = &channelManagerEntry{
  60. channel: NewChannel(server, name, info),
  61. pendingJoins: 0,
  62. }
  63. cm.chans[casefoldedName] = entry
  64. }
  65. }
  66. entry.pendingJoins += 1
  67. cm.Unlock()
  68. entry.channel.Join(client, key, isSajoin, rb)
  69. cm.maybeCleanup(entry.channel, true)
  70. return nil
  71. }
  72. func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
  73. cm.Lock()
  74. defer cm.Unlock()
  75. entry := cm.chans[channel.NameCasefolded()]
  76. if entry == nil || entry.channel != channel {
  77. return
  78. }
  79. if afterJoin {
  80. entry.pendingJoins -= 1
  81. }
  82. // TODO(slingamn) right now, registered channels cannot be cleaned up.
  83. // this is because once ChannelManager becomes the source of truth about a channel,
  84. // we can't move the source of truth back to the database unless we do an ACID
  85. // store while holding the ChannelManager's Lock(). This is pending more decisions
  86. // about where the database transaction lock fits into the overall lock model.
  87. if !entry.channel.IsRegistered() && entry.channel.IsEmpty() && entry.pendingJoins == 0 {
  88. // reread the name, handling the case where the channel was renamed
  89. casefoldedName := entry.channel.NameCasefolded()
  90. delete(cm.chans, casefoldedName)
  91. // invalidate the entry (otherwise, a subsequent cleanup attempt could delete
  92. // a valid, distinct entry under casefoldedName):
  93. entry.channel = nil
  94. }
  95. }
  96. // Part parts `client` from the channel named `name`, deleting it if it's empty.
  97. func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
  98. casefoldedName, err := CasefoldChannel(name)
  99. if err != nil {
  100. return errNoSuchChannel
  101. }
  102. cm.RLock()
  103. entry := cm.chans[casefoldedName]
  104. cm.RUnlock()
  105. if entry == nil {
  106. return errNoSuchChannel
  107. }
  108. entry.channel.Part(client, message, rb)
  109. return nil
  110. }
  111. func (cm *ChannelManager) Cleanup(channel *Channel) {
  112. cm.maybeCleanup(channel, false)
  113. }
  114. // Rename renames a channel (but does not notify the members)
  115. func (cm *ChannelManager) Rename(name string, newname string) error {
  116. cfname, err := CasefoldChannel(name)
  117. if err != nil {
  118. return errNoSuchChannel
  119. }
  120. cfnewname, err := CasefoldChannel(newname)
  121. if err != nil {
  122. return errInvalidChannelName
  123. }
  124. cm.Lock()
  125. defer cm.Unlock()
  126. if cm.chans[cfnewname] != nil {
  127. return errChannelNameInUse
  128. }
  129. entry := cm.chans[cfname]
  130. if entry == nil {
  131. return errNoSuchChannel
  132. }
  133. delete(cm.chans, cfname)
  134. cm.chans[cfnewname] = entry
  135. entry.channel.setName(newname)
  136. entry.channel.setNameCasefolded(cfnewname)
  137. return nil
  138. }
  139. // Len returns the number of channels
  140. func (cm *ChannelManager) Len() int {
  141. cm.RLock()
  142. defer cm.RUnlock()
  143. return len(cm.chans)
  144. }
  145. // Channels returns a slice containing all current channels
  146. func (cm *ChannelManager) Channels() (result []*Channel) {
  147. cm.RLock()
  148. defer cm.RUnlock()
  149. for _, entry := range cm.chans {
  150. result = append(result, entry.channel)
  151. }
  152. return
  153. }