github.com/sttk/sabi@v0.5.0/dax.go (about) 1 // Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved. 2 // This program is free software under MIT License. 3 // See the file LICENSE in this distribution for more details. 4 5 package sabi 6 7 import ( 8 "reflect" 9 "sync" 10 ) 11 12 type /* error reasons */ ( 13 // FailToStartUpGlobalDaxSrcs is an error reason which indicates that some 14 // dax sources failed to start up. 15 // The field: Errors is a map of which keys are the registered names of 16 // DaxSrc(s) which failed to start up, and of which values are Err having 17 // their error reasons. 18 FailToStartUpGlobalDaxSrcs struct { 19 Errors map[string]Err 20 } 21 22 // DaxSrcIsNotFound is an error reason which indicates that a specified data 23 // source is not found. 24 // The field: Name is a registered name of a DaxSrc not found. 25 DaxSrcIsNotFound struct { 26 Name string 27 } 28 29 // FailToCreateDaxConn is an error reason which indicates that it's failed to 30 // create a new connection to a data store. 31 // The field: Name is a registered name of a DaxSrc which failed to create a 32 // DaxConn. 33 FailToCreateDaxConn struct { 34 Name string 35 } 36 37 // FailToCastConn is an error reason which indicates that it is failed to 38 // cast type of a DaxConn. 39 // The field: Name is a registered name of a DaxSrc which created the target 40 // DaxConn. 41 // And the fields: FromType and ToType are the types of the source DaxConn 42 // and the destination DaxConn. 43 FailToCastDaxConn struct { 44 Name, FromType, ToType string 45 } 46 47 // FailToCommitDaxConn is an error reason which indicates that some 48 // connections failed to commit. 49 // The field: Errors is a map of which keys are the registered names of 50 // DaxConn which failed to commit, and of which values are Err having their 51 // error reasons. 52 FailToCommitDaxConn struct { 53 Errors map[string]Err 54 } 55 ) 56 57 // DaxConn is an interface which represents a connection to a data store, and 58 // defines methods: Commit, Rollback and Close to work in a tranaction process. 59 type DaxConn interface { 60 Commit() Err 61 Rollback() 62 Close() 63 } 64 65 // DaxSrc is an interface which represents a data connection source for a data 66 // store like database, etc., and creates a DaxConn which is a connection to a 67 // data store. 68 // This interface defines a method: CreateDaxConn to creates a DaxConn instance 69 // and returns its pointer. 70 // This interface also defines methods: SetUp and End, which makes available 71 // and frees this dax source. 72 type DaxSrc interface { 73 CreateDaxConn() (DaxConn, Err) 74 SetUp() Err 75 End() 76 } 77 78 // Dax is an interface for a set of data access methods. 79 // This method gets a DaxConn which is a connection to a data store by 80 // specified name. 81 // If a DaxConn is found, this method returns it, but not found, creates a new 82 // one with a local or global DaxSrc associated with same name. 83 // If there are both local and global DaxSrc with same name, the local DaxSrc 84 // is used. 85 type Dax interface { 86 getDaxConn(name string) (DaxConn, Err) 87 } 88 89 var ( 90 isGlobalDaxSrcsFixed bool = false 91 globalDaxSrcMap map[string]DaxSrc = make(map[string]DaxSrc) 92 globalDaxSrcMutex sync.Mutex 93 ) 94 95 // AddGlobalDaxSrc registers a global DaxSrc with its name to make enable to 96 // use DaxSrc in all transactions. 97 // This method ignores to add a global DaxSrc when its name is already 98 // registered. 99 // In addition, this method ignores to add any more global DaxSrc(s) after 100 // calling FixGlobalDaxSrcs function. 101 func AddGlobalDaxSrc(name string, ds DaxSrc) { 102 globalDaxSrcMutex.Lock() 103 defer globalDaxSrcMutex.Unlock() 104 105 if !isGlobalDaxSrcsFixed { 106 _, exists := globalDaxSrcMap[name] 107 if !exists { 108 globalDaxSrcMap[name] = ds 109 } 110 } 111 } 112 113 // StartUpGlobalDaxSrcs is a function to forbid adding more global dax sources 114 // and to make available the registered global dax sources by calling Setup 115 // method of each DaxSrc. 116 // If even one DaxSrc fail to execute its SstUp method, this function 117 // executes Free methods of all global DaxSrc(s) and returns sabi.Err. 118 func StartUpGlobalDaxSrcs() Err { 119 isGlobalDaxSrcsFixed = true 120 121 ch := make(chan namedErr) 122 123 for name, ds := range globalDaxSrcMap { 124 go func(name string, ds DaxSrc, ch chan namedErr) { 125 err := ds.SetUp() 126 ne := namedErr{name: name, err: err} 127 ch <- ne 128 }(name, ds, ch) 129 } 130 131 errs := make(map[string]Err) 132 n := len(globalDaxSrcMap) 133 for i := 0; i < n; i++ { 134 select { 135 case ne := <-ch: 136 if !ne.err.IsOk() { 137 errs[ne.name] = ne.err 138 } 139 } 140 } 141 142 if len(errs) > 0 { 143 ShutdownGlobalDaxSrcs() 144 return NewErr(FailToStartUpGlobalDaxSrcs{Errors: errs}) 145 } 146 147 return Ok() 148 } 149 150 // ShutdownGlobalDaxSrcs is a function to terminate all global dax sources. 151 func ShutdownGlobalDaxSrcs() { 152 var wg sync.WaitGroup 153 wg.Add(len(globalDaxSrcMap)) 154 155 for _, ds := range globalDaxSrcMap { 156 go func(ds DaxSrc) { 157 defer wg.Done() 158 ds.End() 159 }(ds) 160 } 161 162 wg.Wait() 163 } 164 165 // DaxBase is an interface which works as a front of an implementation as a 166 // base of data connection sources, and defines methods: SetUpLocalDaxSrc and 167 // FreeLocalDaxSrc. 168 // 169 // SetUpLocalDaxSrc method registered a DaxSrc with a name in this 170 // implementation, but ignores to add a local DaxSrc when its name is already 171 // registered. 172 // In addition, this method ignores to add local DaxSrc(s) while the 173 // transaction is processing. 174 // 175 // This interface inherits Dax interface to get a DaxConn by a name. 176 // Also, this has unexported methods for a transaction process. 177 type DaxBase interface { 178 Dax 179 SetUpLocalDaxSrc(name string, ds DaxSrc) Err 180 FreeLocalDaxSrc(name string) 181 FreeAllLocalDaxSrcs() 182 begin() 183 commit() Err 184 rollback() 185 end() 186 } 187 188 type daxBaseImpl struct { 189 isLocalDaxSrcsFixed bool 190 localDaxSrcMap map[string]DaxSrc 191 daxConnMap map[string]DaxConn 192 daxConnMutex sync.Mutex 193 } 194 195 // NewDaxBase is a function which creates a new DaxBase. 196 func NewDaxBase() DaxBase { 197 return &daxBaseImpl{ 198 isLocalDaxSrcsFixed: false, 199 localDaxSrcMap: make(map[string]DaxSrc), 200 daxConnMap: make(map[string]DaxConn), 201 } 202 } 203 204 func (base *daxBaseImpl) SetUpLocalDaxSrc(name string, ds DaxSrc) Err { 205 base.daxConnMutex.Lock() 206 defer base.daxConnMutex.Unlock() 207 208 if !base.isLocalDaxSrcsFixed { 209 _, exists := base.localDaxSrcMap[name] 210 if !exists { 211 err := ds.SetUp() 212 if !err.IsOk() { 213 return err 214 } 215 base.localDaxSrcMap[name] = ds 216 } 217 } 218 219 return Ok() 220 } 221 222 func (base *daxBaseImpl) FreeLocalDaxSrc(name string) { 223 base.daxConnMutex.Lock() 224 defer base.daxConnMutex.Unlock() 225 226 if !base.isLocalDaxSrcsFixed { 227 ds, exists := base.localDaxSrcMap[name] 228 if exists { 229 delete(base.localDaxSrcMap, name) 230 ds.End() 231 } 232 } 233 } 234 235 func (base *daxBaseImpl) FreeAllLocalDaxSrcs() { 236 base.daxConnMutex.Lock() 237 defer base.daxConnMutex.Unlock() 238 239 if !base.isLocalDaxSrcsFixed { 240 for _, ds := range base.localDaxSrcMap { 241 ds.End() 242 } 243 244 base.localDaxSrcMap = make(map[string]DaxSrc) 245 } 246 } 247 248 func (base *daxBaseImpl) getDaxConn(name string) (DaxConn, Err) { 249 conn := base.daxConnMap[name] 250 if conn != nil { 251 return conn, Ok() 252 } 253 254 ds := base.localDaxSrcMap[name] 255 if ds == nil { 256 ds = globalDaxSrcMap[name] 257 } 258 if ds == nil { 259 return nil, NewErr(DaxSrcIsNotFound{Name: name}) 260 } 261 262 base.daxConnMutex.Lock() 263 defer base.daxConnMutex.Unlock() 264 265 conn = base.daxConnMap[name] 266 if conn != nil { 267 return conn, Ok() 268 } 269 270 var err Err 271 conn, err = ds.CreateDaxConn() 272 if !err.IsOk() { 273 return nil, NewErr(FailToCreateDaxConn{Name: name}, err) 274 } 275 276 base.daxConnMap[name] = conn 277 278 return conn, Ok() 279 } 280 281 func (base *daxBaseImpl) begin() { 282 base.isLocalDaxSrcsFixed = true 283 isGlobalDaxSrcsFixed = true 284 } 285 286 type namedErr struct { 287 name string 288 err Err 289 } 290 291 func (base *daxBaseImpl) commit() Err { 292 ch := make(chan namedErr) 293 294 for name, conn := range base.daxConnMap { 295 go func(name string, conn DaxConn, ch chan namedErr) { 296 err := conn.Commit() 297 ne := namedErr{name: name, err: err} 298 ch <- ne 299 }(name, conn, ch) 300 } 301 302 errs := make(map[string]Err) 303 n := len(base.daxConnMap) 304 for i := 0; i < n; i++ { 305 select { 306 case ne := <-ch: 307 if !ne.err.IsOk() { 308 errs[ne.name] = ne.err 309 } 310 } 311 } 312 313 if len(errs) > 0 { 314 return NewErr(FailToCommitDaxConn{Errors: errs}) 315 } 316 317 return Ok() 318 } 319 320 func (base *daxBaseImpl) rollback() { 321 var wg sync.WaitGroup 322 wg.Add(len(base.daxConnMap)) 323 324 for _, conn := range base.daxConnMap { 325 go func(conn DaxConn) { 326 defer wg.Done() 327 conn.Rollback() 328 }(conn) 329 } 330 331 wg.Wait() 332 } 333 334 func (base *daxBaseImpl) end() { 335 var wg sync.WaitGroup 336 wg.Add(len(base.daxConnMap)) 337 338 for _, conn := range base.daxConnMap { 339 go func(conn DaxConn) { 340 defer wg.Done() 341 conn.Close() 342 }(conn) 343 } 344 345 base.daxConnMap = make(map[string]DaxConn) 346 347 wg.Wait() 348 349 base.isLocalDaxSrcsFixed = false 350 } 351 352 // GetDaxConn is a function to cast type of DaxConn instance. 353 // If it's failed to cast to a destination type, this function returns an Err 354 // of a reason: FailToGetDaxConn. 355 func GetDaxConn[C DaxConn](dax Dax, name string) (C, Err) { 356 conn, err := dax.getDaxConn(name) 357 if err.IsOk() { 358 casted, ok := conn.(C) 359 if ok { 360 return casted, err 361 } 362 363 var from string 364 t := reflect.TypeOf(conn) 365 if t.Kind() == reflect.Ptr { 366 t = t.Elem() 367 from = "*" + t.Name() + " (" + t.PkgPath() + ")" 368 } else { 369 from = t.Name() + " (" + t.PkgPath() + ")" 370 } 371 372 var to string 373 t = reflect.TypeOf(casted) 374 if t.Kind() == reflect.Ptr { 375 t = t.Elem() 376 to = "*" + t.Name() + " (" + t.PkgPath() + ")" 377 } else { 378 to = t.Name() + " (" + t.PkgPath() + ")" 379 } 380 err = NewErr(FailToCastDaxConn{Name: name, FromType: from, ToType: to}) 381 } 382 383 return *new(C), err 384 }