github.com/matrixorigin/matrixone@v0.7.0/pkg/taskservice/task_service_holder.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package taskservice
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sync"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/log"
    23  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    24  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    25  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    26  	logservicepb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
    27  	"github.com/matrixorigin/matrixone/pkg/pb/task"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  var (
    32  	errNotReady = moerr.NewInvalidStateNoCtx("task store not ready")
    33  )
    34  
    35  type taskServiceHolder struct {
    36  	rt                         runtime.Runtime
    37  	addressFactory             func(context.Context) (string, error)
    38  	taskStorageFactorySelector func(string, string, string) TaskStorageFactory
    39  	mu                         struct {
    40  		sync.RWMutex
    41  		closed  bool
    42  		store   TaskStorage
    43  		service TaskService
    44  	}
    45  }
    46  
    47  // NewTaskServiceHolder create a task service hold, it will create task storage and task service from the hakeeper's schedule command.
    48  func NewTaskServiceHolder(
    49  	rt runtime.Runtime,
    50  	addressFactory func(context.Context) (string, error)) TaskServiceHolder {
    51  	return NewTaskServiceHolderWithTaskStorageFactorySelector(rt, addressFactory, func(username, password, database string) TaskStorageFactory {
    52  		return NewMySQLBasedTaskStorageFactory(username, password, database)
    53  	})
    54  }
    55  
    56  // NewTaskServiceHolderWithTaskStorageFactorySelector is similar to NewTaskServiceHolder, but with a special
    57  // task storage facroty selector
    58  func NewTaskServiceHolderWithTaskStorageFactorySelector(
    59  	rt runtime.Runtime,
    60  	addressFactory func(context.Context) (string, error),
    61  	selector func(string, string, string) TaskStorageFactory) TaskServiceHolder {
    62  	return &taskServiceHolder{
    63  		rt:                         rt,
    64  		addressFactory:             addressFactory,
    65  		taskStorageFactorySelector: selector,
    66  	}
    67  }
    68  
    69  func (h *taskServiceHolder) Close() error {
    70  	defer h.rt.Logger().LogAction("close service-holder",
    71  		log.DefaultLogOptions().WithLevel(zap.DebugLevel))()
    72  
    73  	h.mu.Lock()
    74  	defer h.mu.Unlock()
    75  
    76  	if h.mu.closed {
    77  		return nil
    78  	}
    79  	h.mu.closed = true
    80  	if h.mu.store == nil {
    81  		return nil
    82  	}
    83  	return h.mu.service.Close()
    84  }
    85  
    86  func (h *taskServiceHolder) Create(command logservicepb.CreateTaskService) error {
    87  	// TODO: In any case, the username and password are not printed in the log, morpc needs to fix
    88  	if command.User.Username == "" || command.User.Password == "" {
    89  		h.rt.Logger().Debug("start task runner skipped",
    90  			zap.String("reason", "empty task user and passwd"))
    91  		return moerr.NewInvalidStateNoCtx("empty task user and passwd")
    92  	}
    93  
    94  	h.mu.Lock()
    95  	defer h.mu.Unlock()
    96  	if h.mu.service != nil {
    97  		return nil
    98  	}
    99  
   100  	store := newRefreshableTaskStorage(
   101  		h.rt,
   102  		h.addressFactory,
   103  		h.taskStorageFactorySelector(command.User.Username,
   104  			command.User.Password,
   105  			command.TaskDatabase))
   106  	h.mu.store = store
   107  	h.mu.service = NewTaskService(h.rt, store)
   108  	return nil
   109  }
   110  
   111  func (h *taskServiceHolder) Get() (TaskService, bool) {
   112  	h.mu.RLock()
   113  	defer h.mu.RUnlock()
   114  	if h.mu.service == nil {
   115  		return nil, false
   116  	}
   117  	return h.mu.service, true
   118  }
   119  
   120  type refreshableTaskStorage struct {
   121  	rt             runtime.Runtime
   122  	refreshC       chan string
   123  	stopper        *stopper.Stopper
   124  	addressFactory func(context.Context) (string, error)
   125  	storeFactory   TaskStorageFactory
   126  	mu             struct {
   127  		sync.RWMutex
   128  		closed      bool
   129  		lastAddress string
   130  		store       TaskStorage
   131  	}
   132  }
   133  
   134  func newRefreshableTaskStorage(
   135  	rt runtime.Runtime,
   136  	addressFactory func(context.Context) (string, error),
   137  	storeFactory TaskStorageFactory) TaskStorage {
   138  	s := &refreshableTaskStorage{
   139  		rt:             rt,
   140  		refreshC:       make(chan string, 1),
   141  		addressFactory: addressFactory,
   142  		storeFactory:   storeFactory,
   143  		stopper: stopper.NewStopper("refresh-taskstorage",
   144  			stopper.WithLogger(rt.Logger().RawLogger())),
   145  	}
   146  	s.refresh(context.Background(), "")
   147  	if err := s.stopper.RunTask(s.refreshTask); err != nil {
   148  		panic(err)
   149  	}
   150  	return s
   151  }
   152  
   153  func (s *refreshableTaskStorage) Close() error {
   154  	defer s.rt.Logger().LogAction("close refreshable-storage",
   155  		log.DefaultLogOptions().WithLevel(zap.DebugLevel))()
   156  
   157  	var err error
   158  	s.mu.Lock()
   159  	if s.mu.closed {
   160  		s.mu.Unlock()
   161  		return nil
   162  	}
   163  	s.mu.closed = true
   164  	if s.mu.store != nil {
   165  		err = s.mu.store.Close()
   166  	}
   167  	s.mu.Unlock()
   168  	s.stopper.Stop()
   169  	close(s.refreshC)
   170  	return err
   171  }
   172  
   173  func (s *refreshableTaskStorage) Add(ctx context.Context, tasks ...task.Task) (int, error) {
   174  	var v int
   175  	var err error
   176  	s.mu.RLock()
   177  	lastAddress := s.mu.lastAddress
   178  	if s.mu.store == nil {
   179  		err = errNotReady
   180  	} else {
   181  		v, err = s.mu.store.Add(ctx, tasks...)
   182  	}
   183  	s.mu.RUnlock()
   184  	if err != nil {
   185  		s.maybeRefresh(lastAddress)
   186  	}
   187  	return v, err
   188  }
   189  
   190  func (s *refreshableTaskStorage) Update(ctx context.Context, tasks []task.Task, conditions ...Condition) (int, error) {
   191  	var v int
   192  	var err error
   193  	s.mu.RLock()
   194  	lastAddress := s.mu.lastAddress
   195  	if s.mu.store == nil {
   196  		err = errNotReady
   197  	} else {
   198  		v, err = s.mu.store.Update(ctx, tasks, conditions...)
   199  	}
   200  	s.mu.RUnlock()
   201  	if err != nil {
   202  		s.maybeRefresh(lastAddress)
   203  	}
   204  	return v, err
   205  }
   206  
   207  func (s *refreshableTaskStorage) Delete(ctx context.Context, conditions ...Condition) (int, error) {
   208  	var v int
   209  	var err error
   210  	s.mu.RLock()
   211  	lastAddress := s.mu.lastAddress
   212  	if s.mu.store == nil {
   213  		err = errNotReady
   214  	} else {
   215  		v, err = s.mu.store.Delete(ctx, conditions...)
   216  	}
   217  	s.mu.RUnlock()
   218  	if err != nil {
   219  		s.maybeRefresh(lastAddress)
   220  	}
   221  	return v, err
   222  }
   223  
   224  func (s *refreshableTaskStorage) Query(ctx context.Context, conditions ...Condition) ([]task.Task, error) {
   225  	var v []task.Task
   226  	var err error
   227  	s.mu.RLock()
   228  	lastAddress := s.mu.lastAddress
   229  	if s.mu.store == nil {
   230  		err = errNotReady
   231  	} else {
   232  		v, err = s.mu.store.Query(ctx, conditions...)
   233  	}
   234  	s.mu.RUnlock()
   235  	if err != nil {
   236  		s.maybeRefresh(lastAddress)
   237  	}
   238  	return v, err
   239  }
   240  
   241  func (s *refreshableTaskStorage) AddCronTask(ctx context.Context, tasks ...task.CronTask) (int, error) {
   242  	var v int
   243  	var err error
   244  	s.mu.RLock()
   245  	lastAddress := s.mu.lastAddress
   246  	if s.mu.store == nil {
   247  		err = errNotReady
   248  	} else {
   249  		v, err = s.mu.store.AddCronTask(ctx, tasks...)
   250  	}
   251  	s.mu.RUnlock()
   252  	if err != nil {
   253  		s.maybeRefresh(lastAddress)
   254  	}
   255  	return v, err
   256  }
   257  
   258  func (s *refreshableTaskStorage) QueryCronTask(ctx context.Context) ([]task.CronTask, error) {
   259  	var v []task.CronTask
   260  	var err error
   261  	s.mu.RLock()
   262  	lastAddress := s.mu.lastAddress
   263  	if s.mu.store == nil {
   264  		err = errNotReady
   265  	} else {
   266  		v, err = s.mu.store.QueryCronTask(ctx)
   267  	}
   268  	s.mu.RUnlock()
   269  	if err != nil {
   270  		s.maybeRefresh(lastAddress)
   271  	}
   272  	return v, err
   273  }
   274  
   275  func (s *refreshableTaskStorage) UpdateCronTask(ctx context.Context, cronTask task.CronTask, task task.Task) (int, error) {
   276  	var v int
   277  	var err error
   278  	s.mu.RLock()
   279  	lastAddress := s.mu.lastAddress
   280  	if s.mu.store == nil {
   281  		err = errNotReady
   282  	} else {
   283  		v, err = s.mu.store.UpdateCronTask(ctx, cronTask, task)
   284  	}
   285  	s.mu.RUnlock()
   286  	if err != nil {
   287  		s.maybeRefresh(lastAddress)
   288  	}
   289  	return v, err
   290  }
   291  
   292  func (s *refreshableTaskStorage) maybeRefresh(lastAddress string) bool {
   293  	s.mu.Lock()
   294  	defer s.mu.Unlock()
   295  	if s.mu.closed {
   296  		return false
   297  	}
   298  
   299  	select {
   300  	case s.refreshC <- lastAddress:
   301  		return true
   302  	default:
   303  		return false
   304  	}
   305  }
   306  
   307  func (s *refreshableTaskStorage) refreshTask(ctx context.Context) {
   308  	defer s.rt.Logger().LogAction("close refresh-task",
   309  		log.DefaultLogOptions().WithLevel(zap.DebugLevel))()
   310  
   311  	for {
   312  		select {
   313  		case <-ctx.Done():
   314  			return
   315  		case lastAddress := <-s.refreshC:
   316  			s.refresh(ctx, lastAddress)
   317  			// see pkg/logservice/service_commands.go#132
   318  			select {
   319  			case <-ctx.Done():
   320  				return
   321  			default:
   322  			}
   323  		}
   324  	}
   325  }
   326  
   327  func (s *refreshableTaskStorage) refresh(ctx context.Context, lastAddress string) {
   328  	s.mu.Lock()
   329  	defer s.mu.Unlock()
   330  
   331  	if s.mu.store != nil {
   332  		_ = s.mu.store.Close()
   333  	}
   334  
   335  	if s.mu.closed {
   336  		return
   337  	}
   338  	if lastAddress != "" && lastAddress != s.mu.lastAddress {
   339  		return
   340  	}
   341  	connectAddress, err := s.addressFactory(ctx)
   342  	if err != nil {
   343  		s.rt.Logger().Error("failed to refresh task storage",
   344  			zap.Error(err))
   345  		return
   346  	}
   347  
   348  	s.mu.lastAddress = connectAddress
   349  	s.rt.Logger().Debug("trying to refresh task storage", zap.String("address", connectAddress))
   350  	store, err := s.storeFactory.Create(connectAddress)
   351  	if err != nil {
   352  		s.rt.Logger().Error("failed to refresh task storage",
   353  			zap.String("address", connectAddress),
   354  			zap.Error(err))
   355  		return
   356  	}
   357  	s.mu.store = store
   358  	s.rt.Logger().Debug("refresh task storage completed", zap.String("sql-address", connectAddress))
   359  }
   360  
   361  type mysqlBasedStorageFactory struct {
   362  	username string
   363  	password string
   364  	database string
   365  }
   366  
   367  // NewMySQLBasedTaskStorageFactory creates a mysql based task storage factory using the special username, password and database
   368  func NewMySQLBasedTaskStorageFactory(username, password, database string) TaskStorageFactory {
   369  	return &mysqlBasedStorageFactory{
   370  		username: username,
   371  		password: password,
   372  		database: database,
   373  	}
   374  }
   375  
   376  func (f *mysqlBasedStorageFactory) Create(address string) (TaskStorage, error) {
   377  	dsn := fmt.Sprintf("%s:%s@tcp(%s)/?readTimeout=15s&writeTimeout=15s&timeout=15s",
   378  		f.username,
   379  		f.password,
   380  		address)
   381  	return NewMysqlTaskStorage(dsn, f.database)
   382  }
   383  
   384  type fixedTaskStorageFactory struct {
   385  	store TaskStorage
   386  }
   387  
   388  // NewFixedTaskStorageFactory creates a fixed task storage factory which always returns the special taskstorage
   389  func NewFixedTaskStorageFactory(store TaskStorage) TaskStorageFactory {
   390  	return &fixedTaskStorageFactory{
   391  		store: store,
   392  	}
   393  }
   394  
   395  func (f *fixedTaskStorageFactory) Create(address string) (TaskStorage, error) {
   396  	return f.store, nil
   397  }