dubbo.apache.org/dubbo-go/v3@v3.1.1/config_center/file/listener.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package file 19 20 import ( 21 "os" 22 "sync" 23 24 "dubbo.apache.org/dubbo-go/v3/common/extension" 25 "dubbo.apache.org/dubbo-go/v3/config_center" 26 "dubbo.apache.org/dubbo-go/v3/remoting" 27 "github.com/dubbogo/gost/log/logger" 28 "github.com/fsnotify/fsnotify" 29 ) 30 31 // CacheListener is file watcher 32 type CacheListener struct { 33 watch *fsnotify.Watcher 34 keyListeners sync.Map 35 rootPath string 36 } 37 38 // NewCacheListener creates a new CacheListener 39 func NewCacheListener(rootPath string) *CacheListener { 40 cl := &CacheListener{rootPath: rootPath} 41 // start watcher 42 watch, err := fsnotify.NewWatcher() 43 if err != nil { 44 logger.Errorf("file : listen config fail, error:%v ", err) 45 } 46 go func() { 47 for { 48 select { 49 case event := <-watch.Events: 50 key := event.Name 51 logger.Debugf("watcher %s, event %v", cl.rootPath, event) 52 if event.Op&fsnotify.Write == fsnotify.Write { 53 if l, ok := cl.keyListeners.Load(key); ok { 54 dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, 55 remoting.EventTypeUpdate) 56 } 57 } 58 if event.Op&fsnotify.Create == fsnotify.Create { 59 if l, ok := cl.keyListeners.Load(key); ok { 60 dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, 61 remoting.EventTypeAdd) 62 } 63 } 64 if event.Op&fsnotify.Remove == fsnotify.Remove { 65 if l, ok := cl.keyListeners.Load(key); ok { 66 removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel) 67 } 68 } 69 case err := <-watch.Errors: 70 // err may be nil, ignore 71 if err != nil { 72 logger.Warnf("file : listen watch fail:%+v", err) 73 } 74 } 75 } 76 }() 77 cl.watch = watch 78 79 extension.AddCustomShutdownCallback(func() { 80 cl.watch.Close() 81 }) 82 83 return cl 84 } 85 86 func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) { 87 if len(lmap) == 0 { 88 logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event) 89 return 90 } 91 for l := range lmap { 92 callback(l, key, "", event) 93 } 94 } 95 96 func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) { 97 if len(lmap) == 0 { 98 logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event) 99 return 100 } 101 c := getFileContent(key) 102 for l := range lmap { 103 callback(l, key, c, event) 104 } 105 } 106 107 func callback(listener config_center.ConfigurationListener, path, data string, event remoting.EventType) { 108 listener.Process(&config_center.ConfigChangeEvent{Key: path, Value: data, ConfigType: event}) 109 } 110 111 // Close will remove key listener and close watcher 112 func (cl *CacheListener) Close() error { 113 cl.keyListeners.Range(func(key, value interface{}) bool { 114 cl.keyListeners.Delete(key) 115 return true 116 }) 117 return cl.watch.Close() 118 } 119 120 // AddListener will add a listener if loaded 121 // if you watcher a file or directory not exist, will error with no such file or directory 122 func (cl *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) { 123 // reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure 124 // make a map[your type]struct{} like set in java 125 listeners, loaded := cl.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{ 126 listener: {}, 127 }) 128 if loaded { 129 listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{} 130 cl.keyListeners.Store(key, listeners) 131 return 132 } 133 if err := cl.watch.Add(key); err != nil { 134 logger.Errorf("watcher add path:%s err:%v", key, err) 135 } 136 } 137 138 // RemoveListener will delete a listener if loaded 139 func (cl *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) { 140 listeners, loaded := cl.keyListeners.Load(key) 141 if !loaded { 142 return 143 } 144 delete(listeners.(map[config_center.ConfigurationListener]struct{}), listener) 145 if err := cl.watch.Remove(key); err != nil { 146 logger.Errorf("watcher remove path:%s err:%v", key, err) 147 } 148 } 149 150 func getFileContent(path string) string { 151 c, err := os.ReadFile(path) 152 if err != nil { 153 logger.Errorf("read file path:%s err:%v", path, err) 154 return "" 155 } 156 157 return string(c) 158 }