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.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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, 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, true, 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, rb)
  69. cm.maybeCleanup(entry, true)
  70. return nil
  71. }
  72. func (cm *ChannelManager) maybeCleanup(entry *channelManagerEntry, afterJoin bool) {
  73. cm.Lock()
  74. defer cm.Unlock()
  75. if entry.channel == nil {
  76. return
  77. }
  78. if afterJoin {
  79. entry.pendingJoins -= 1
  80. }
  81. // TODO(slingamn) right now, registered channels cannot be cleaned up.
  82. // this is because once ChannelManager becomes the source of truth about a channel,
  83. // we can't move the source of truth back to the database unless we do an ACID
  84. // store while holding the ChannelManager's Lock(). This is pending more decisions
  85. // about where the database transaction lock fits into the overall lock model.
  86. if !entry.channel.IsRegistered() && entry.channel.IsEmpty() && entry.pendingJoins == 0 {
  87. // reread the name, handling the case where the channel was renamed
  88. casefoldedName := entry.channel.NameCasefolded()
  89. delete(cm.chans, casefoldedName)
  90. // invalidate the entry (otherwise, a subsequent cleanup attempt could delete
  91. // a valid, distinct entry under casefoldedName):
  92. entry.channel = nil
  93. }
  94. }
  95. // Part parts `client` from the channel named `name`, deleting it if it's empty.
  96. func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
  97. casefoldedName, err := CasefoldChannel(name)
  98. if err != nil {
  99. return errNoSuchChannel
  100. }
  101. cm.RLock()
  102. entry := cm.chans[casefoldedName]
  103. cm.RUnlock()
  104. if entry == nil {
  105. return errNoSuchChannel
  106. }
  107. entry.channel.Part(client, message, rb)
  108. cm.maybeCleanup(entry, false)
  109. return nil
  110. }
  111. // Rename renames a channel (but does not notify the members)
  112. func (cm *ChannelManager) Rename(name string, newname string) error {
  113. cfname, err := CasefoldChannel(name)
  114. if err != nil {
  115. return errNoSuchChannel
  116. }
  117. cfnewname, err := CasefoldChannel(newname)
  118. if err != nil {
  119. return errInvalidChannelName
  120. }
  121. cm.Lock()
  122. defer cm.Unlock()
  123. if cm.chans[cfnewname] != nil {
  124. return errChannelNameInUse
  125. }
  126. entry := cm.chans[cfname]
  127. if entry == nil {
  128. return errNoSuchChannel
  129. }
  130. delete(cm.chans, cfname)
  131. cm.chans[cfnewname] = entry
  132. entry.channel.setName(newname)
  133. entry.channel.setNameCasefolded(cfnewname)
  134. return nil
  135. }
  136. // Len returns the number of channels
  137. func (cm *ChannelManager) Len() int {
  138. cm.RLock()
  139. defer cm.RUnlock()
  140. return len(cm.chans)
  141. }
  142. // Channels returns a slice containing all current channels
  143. func (cm *ChannelManager) Channels() (result []*Channel) {
  144. cm.RLock()
  145. defer cm.RUnlock()
  146. for _, entry := range cm.chans {
  147. result = append(result, entry.channel)
  148. }
  149. return
  150. }