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  }