Left: | ||
Right: |
OLD | NEW |
---|---|
1 // Copyright 2011 The Go Authors. All rights reserved. | 1 // Copyright 2011 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 // Package sql provides a generic interface around SQL (or SQL-like) | 5 // Package sql provides a generic interface around SQL (or SQL-like) |
6 // databases. | 6 // databases. |
7 // | 7 // |
8 // The sql package must be used in conjunction with a database driver. | 8 // The sql package must be used in conjunction with a database driver. |
9 // See http://golang.org/s/sqldrivers for a list of drivers. | 9 // See http://golang.org/s/sqldrivers for a list of drivers. |
10 package sql | 10 package sql |
(...skipping 189 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
200 maxIdle int // zero means defaultMaxIdleConns; negat ive means 0 | 200 maxIdle int // zero means defaultMaxIdleConns; negat ive means 0 |
201 } | 201 } |
202 | 202 |
203 // driverConn wraps a driver.Conn with a mutex, to | 203 // driverConn wraps a driver.Conn with a mutex, to |
204 // be held during all calls into the Conn. (including any calls onto | 204 // be held during all calls into the Conn. (including any calls onto |
205 // interfaces returned via that Conn, such as calls on Tx, Stmt, | 205 // interfaces returned via that Conn, such as calls on Tx, Stmt, |
206 // Result, Rows) | 206 // Result, Rows) |
207 type driverConn struct { | 207 type driverConn struct { |
208 db *DB | 208 db *DB |
209 | 209 |
210 » sync.Mutex // guards following | 210 » sync.Mutex // guards following |
211 » ci driver.Conn | 211 » ci driver.Conn |
212 » closed bool | 212 » closed bool |
213 » finalClosed bool // ci.Close has been called | |
214 » openStmt map[driver.Stmt]bool | |
213 | 215 |
214 // guarded by db.mu | 216 // guarded by db.mu |
215 » inUse bool | 217 » inUse bool |
216 » onPut []func() // code (with db.mu held) run when conn is next returned | 218 » onPut []func() // code (with db.mu held) run when conn is next retu rned |
219 » dbmuClosed bool // same as closed, but guarded by db.mu, for connIfF ree | |
220 } | |
221 | |
222 func (dc *driverConn) removeOpenStmt(si driver.Stmt) { | |
223 » dc.Lock() | |
224 » defer dc.Unlock() | |
225 » delete(dc.openStmt, si) | |
226 } | |
227 | |
228 func (dc *driverConn) prepareLocked(query string) (driver.Stmt, error) { | |
229 » si, err := dc.ci.Prepare(query) | |
230 » if err == nil { | |
231 » » // Track each driverConn's open statements, so we can close them | |
232 » » // before closing the conn. | |
233 » » // | |
234 » » // TODO(bradfitz): let drivers opt-out of caring about | |
r
2013/04/25 20:09:17
s/-/ / (it's a verb not an adjective)
bradfitz
2013/04/25 21:46:10
Done.
| |
235 » » // stmt closes if the conn is about to close anyway? For now | |
236 » » // do the safe thing, in case stmts need to be closed. | |
237 » » // | |
238 » » // TODO(bradfitz): after Go 1.1, closing driver.Stmts | |
239 » » // should be moved to driverStmt, using unique | |
240 » » // *driverStmts everywhere (including from | |
241 » » // *Stmt.connStmt, instead of returning a | |
242 » » // driver.Stmt), using driverStmt as a pointer | |
243 » » // everywhere, and making it a finalCloser. | |
244 » » if dc.openStmt == nil { | |
245 » » » dc.openStmt = make(map[driver.Stmt]bool) | |
246 » » } | |
247 » » dc.openStmt[si] = true | |
248 » } | |
249 » return si, err | |
217 } | 250 } |
218 | 251 |
219 // the dc.db's Mutex is held. | 252 // the dc.db's Mutex is held. |
220 func (dc *driverConn) closeDBLocked() error { | 253 func (dc *driverConn) closeDBLocked() error { |
221 dc.Lock() | 254 dc.Lock() |
222 if dc.closed { | 255 if dc.closed { |
223 dc.Unlock() | 256 dc.Unlock() |
224 return errors.New("sql: duplicate driverConn close") | 257 return errors.New("sql: duplicate driverConn close") |
225 } | 258 } |
226 dc.closed = true | 259 dc.closed = true |
227 dc.Unlock() // not defer; removeDep finalClose calls may need to lock | 260 dc.Unlock() // not defer; removeDep finalClose calls may need to lock |
228 return dc.db.removeDepLocked(dc, dc)() | 261 return dc.db.removeDepLocked(dc, dc)() |
229 } | 262 } |
230 | 263 |
231 func (dc *driverConn) Close() error { | 264 func (dc *driverConn) Close() error { |
232 dc.Lock() | 265 dc.Lock() |
233 if dc.closed { | 266 if dc.closed { |
234 dc.Unlock() | 267 dc.Unlock() |
235 return errors.New("sql: duplicate driverConn close") | 268 return errors.New("sql: duplicate driverConn close") |
236 } | 269 } |
237 dc.closed = true | 270 dc.closed = true |
238 dc.Unlock() // not defer; removeDep finalClose calls may need to lock | 271 dc.Unlock() // not defer; removeDep finalClose calls may need to lock |
239 » return dc.db.removeDep(dc, dc) | 272 |
273 » // And now updates that require holding dc.mu.Lock() | |
274 » dc.db.mu.Lock() | |
275 » dc.dbmuClosed = true | |
276 » fn := dc.db.removeDepLocked(dc, dc) | |
277 » dc.db.mu.Unlock() | |
278 » return fn() | |
240 } | 279 } |
241 | 280 |
242 func (dc *driverConn) finalClose() error { | 281 func (dc *driverConn) finalClose() error { |
243 dc.Lock() | 282 dc.Lock() |
283 | |
284 for si := range dc.openStmt { | |
285 si.Close() | |
286 } | |
287 dc.openStmt = nil | |
288 | |
244 err := dc.ci.Close() | 289 err := dc.ci.Close() |
245 dc.ci = nil | 290 dc.ci = nil |
291 dc.finalClosed = true | |
292 | |
246 dc.Unlock() | 293 dc.Unlock() |
247 return err | 294 return err |
248 } | 295 } |
249 | 296 |
250 // driverStmt associates a driver.Stmt with the | 297 // driverStmt associates a driver.Stmt with the |
251 // *driverConn from which it came, so the driverConn's lock can be | 298 // *driverConn from which it came, so the driverConn's lock can be |
252 // held during calls. | 299 // held during calls. |
253 type driverStmt struct { | 300 type driverStmt struct { |
254 sync.Locker // the *driverConn | 301 sync.Locker // the *driverConn |
255 si driver.Stmt | 302 si driver.Stmt |
256 } | 303 } |
257 | 304 |
258 func (ds *driverStmt) Close() error { | 305 func (ds *driverStmt) Close() error { |
259 ds.Lock() | 306 ds.Lock() |
260 defer ds.Unlock() | 307 defer ds.Unlock() |
261 return ds.si.Close() | 308 return ds.si.Close() |
262 } | 309 } |
263 | 310 |
264 // depSet is a finalCloser's outstanding dependencies | 311 // depSet is a finalCloser's outstanding dependencies |
265 type depSet map[interface{}]bool // set of true bools | 312 type depSet map[interface{}]bool // set of true bools |
266 | 313 |
267 // The finalCloser interface is used by (*DB).addDep and (*DB).get | 314 // The finalCloser interface is used by (*DB).addDep and related |
315 // dependency reference counting. | |
268 type finalCloser interface { | 316 type finalCloser interface { |
269 // finalClose is called when the reference count of an object | 317 // finalClose is called when the reference count of an object |
270 // goes to zero. (*DB).mu is not held while calling it. | 318 // goes to zero. (*DB).mu is not held while calling it. |
271 finalClose() error | 319 finalClose() error |
272 } | 320 } |
273 | 321 |
274 // addDep notes that x now depends on dep, and x's finalClose won't be | 322 // addDep notes that x now depends on dep, and x's finalClose won't be |
275 // called until all of x's dependencies are removed with removeDep. | 323 // called until all of x's dependencies are removed with removeDep. |
276 func (db *DB) addDep(x finalCloser, dep interface{}) { | 324 func (db *DB) addDep(x finalCloser, dep interface{}) { |
277 //println(fmt.Sprintf("addDep(%T %p, %T %p)", x, x, dep, dep)) | 325 //println(fmt.Sprintf("addDep(%T %p, %T %p)", x, x, dep, dep)) |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
441 db: db, | 489 db: db, |
442 ci: ci, | 490 ci: ci, |
443 } | 491 } |
444 db.mu.Lock() | 492 db.mu.Lock() |
445 db.addDepLocked(dc, dc) | 493 db.addDepLocked(dc, dc) |
446 dc.inUse = true | 494 dc.inUse = true |
447 db.mu.Unlock() | 495 db.mu.Unlock() |
448 return dc, nil | 496 return dc, nil |
449 } | 497 } |
450 | 498 |
451 // connIfFree returns (wanted, true) if wanted is still a valid conn and | 499 var ( |
500 » errConnClosed = errors.New("database/sql: internal sentinel error: conn is closed") | |
501 » errConnBusy = errors.New("database/sql: internal sentinel error: conn is busy") | |
502 ) | |
503 | |
504 // connIfFree returns (wanted, nil) if wanted is still a valid conn and | |
452 // isn't in use. | 505 // isn't in use. |
453 // | 506 // |
454 // If wanted is valid but in use, connIfFree returns (wanted, false). | 507 // The error is errConnClosed if the connection if the requested connection |
455 // If wanted is invalid, connIfFre returns (nil, false). | 508 // is invalid because it's been closed. |
456 func (db *DB) connIfFree(wanted *driverConn) (conn *driverConn, ok bool) { | 509 // |
510 // The error is errConnBusy if the connection is in use. | |
511 func (db *DB) connIfFree(wanted *driverConn) (*driverConn, error) { | |
457 db.mu.Lock() | 512 db.mu.Lock() |
458 defer db.mu.Unlock() | 513 defer db.mu.Unlock() |
459 if wanted.inUse { | 514 if wanted.inUse { |
460 » » return conn, false | 515 » » return nil, errConnBusy |
516 » } | |
517 » if wanted.dbmuClosed { | |
518 » » return nil, errConnClosed | |
461 } | 519 } |
462 for i, conn := range db.freeConn { | 520 for i, conn := range db.freeConn { |
463 if conn != wanted { | 521 if conn != wanted { |
464 continue | 522 continue |
465 } | 523 } |
466 db.freeConn[i] = db.freeConn[len(db.freeConn)-1] | 524 db.freeConn[i] = db.freeConn[len(db.freeConn)-1] |
467 db.freeConn = db.freeConn[:len(db.freeConn)-1] | 525 db.freeConn = db.freeConn[:len(db.freeConn)-1] |
468 wanted.inUse = true | 526 wanted.inUse = true |
469 » » return wanted, true | 527 » » return wanted, nil |
470 } | 528 } |
471 » return nil, false | 529 » // TODO(bradfitz): shouldn't get here. After Go 1.1, change this to: |
530 » // panic("connIfFree call requested a non-closed, non-busy, non-free con n") | |
531 » // Which passes all the tests, but I'm too paranoid to include this | |
532 » // late in Go 1.1. | |
533 » // Instead, treat it like a busy connection: | |
534 » return nil, errConnBusy | |
472 } | 535 } |
473 | 536 |
474 // putConnHook is a hook for testing. | 537 // putConnHook is a hook for testing. |
475 var putConnHook func(*DB, *driverConn) | 538 var putConnHook func(*DB, *driverConn) |
476 | 539 |
477 // noteUnusedDriverStatement notes that si is no longer used and should | 540 // noteUnusedDriverStatement notes that si is no longer used and should |
478 // be closed whenever possible (when c is next not in use), unless c is | 541 // be closed whenever possible (when c is next not in use), unless c is |
479 // already closed. | 542 // already closed. |
480 func (db *DB) noteUnusedDriverStatement(c *driverConn, si driver.Stmt) { | 543 func (db *DB) noteUnusedDriverStatement(c *driverConn, si driver.Stmt) { |
481 db.mu.Lock() | 544 db.mu.Lock() |
482 defer db.mu.Unlock() | 545 defer db.mu.Unlock() |
483 if c.inUse { | 546 if c.inUse { |
484 c.onPut = append(c.onPut, func() { | 547 c.onPut = append(c.onPut, func() { |
485 si.Close() | 548 si.Close() |
486 }) | 549 }) |
487 } else { | 550 } else { |
488 » » si.Close() | 551 » » c.Lock() |
552 » » defer c.Unlock() | |
553 » » if !c.finalClosed { | |
554 » » » si.Close() | |
555 » » } | |
489 } | 556 } |
490 } | 557 } |
491 | 558 |
492 // debugGetPut determines whether getConn & putConn calls' stack traces | 559 // debugGetPut determines whether getConn & putConn calls' stack traces |
493 // are returned for more verbose crashes. | 560 // are returned for more verbose crashes. |
494 const debugGetPut = false | 561 const debugGetPut = false |
495 | 562 |
496 // putConn adds a connection to the db's free pool. | 563 // putConn adds a connection to the db's free pool. |
497 // err is optionally the last error that occurred on this connection. | 564 // err is optionally the last error that occurred on this connection. |
498 func (db *DB) putConn(dc *driverConn, err error) { | 565 func (db *DB) putConn(dc *driverConn, err error) { |
(...skipping 20 matching lines...) Expand all Loading... | |
519 return | 586 return |
520 } | 587 } |
521 if putConnHook != nil { | 588 if putConnHook != nil { |
522 putConnHook(db, dc) | 589 putConnHook(db, dc) |
523 } | 590 } |
524 if n := len(db.freeConn); !db.closed && n < db.maxIdleConnsLocked() { | 591 if n := len(db.freeConn); !db.closed && n < db.maxIdleConnsLocked() { |
525 db.freeConn = append(db.freeConn, dc) | 592 db.freeConn = append(db.freeConn, dc) |
526 db.mu.Unlock() | 593 db.mu.Unlock() |
527 return | 594 return |
528 } | 595 } |
529 // TODO: check to see if we need this Conn for any prepared | |
530 // statements which are still active? | |
531 db.mu.Unlock() | 596 db.mu.Unlock() |
532 | 597 |
533 dc.Close() | 598 dc.Close() |
534 } | 599 } |
535 | 600 |
536 // Prepare creates a prepared statement for later queries or executions. | 601 // Prepare creates a prepared statement for later queries or executions. |
537 // Multiple queries or executions may be run concurrently from the | 602 // Multiple queries or executions may be run concurrently from the |
538 // returned statement. | 603 // returned statement. |
539 func (db *DB) Prepare(query string) (*Stmt, error) { | 604 func (db *DB) Prepare(query string) (*Stmt, error) { |
540 var stmt *Stmt | 605 var stmt *Stmt |
(...skipping 12 matching lines...) Expand all Loading... | |
553 // driver.Preparer interface and call that instead, if so, | 618 // driver.Preparer interface and call that instead, if so, |
554 // otherwise we make a prepared statement that's bound | 619 // otherwise we make a prepared statement that's bound |
555 // to a connection, and to execute this prepared statement | 620 // to a connection, and to execute this prepared statement |
556 // we either need to use this connection (if it's free), else | 621 // we either need to use this connection (if it's free), else |
557 // get a new connection + re-prepare + execute on that one. | 622 // get a new connection + re-prepare + execute on that one. |
558 dc, err := db.conn() | 623 dc, err := db.conn() |
559 if err != nil { | 624 if err != nil { |
560 return nil, err | 625 return nil, err |
561 } | 626 } |
562 dc.Lock() | 627 dc.Lock() |
563 » si, err := dc.ci.Prepare(query) | 628 » si, err := dc.prepareLocked(query) |
564 dc.Unlock() | 629 dc.Unlock() |
565 if err != nil { | 630 if err != nil { |
566 db.putConn(dc, err) | 631 db.putConn(dc, err) |
567 return nil, err | 632 return nil, err |
568 } | 633 } |
569 stmt := &Stmt{ | 634 stmt := &Stmt{ |
570 db: db, | 635 db: db, |
571 query: query, | 636 query: query, |
572 css: []connStmt{{dc, si}}, | 637 css: []connStmt{{dc, si}}, |
573 } | 638 } |
574 db.addDep(stmt, stmt) | 639 db.addDep(stmt, stmt) |
575 db.addDep(dc, stmt) | |
576 db.putConn(dc, nil) | 640 db.putConn(dc, nil) |
577 return stmt, nil | 641 return stmt, nil |
578 } | 642 } |
579 | 643 |
580 // Exec executes a query without returning any rows. | 644 // Exec executes a query without returning any rows. |
581 // The args are for any placeholder parameters in the query. | 645 // The args are for any placeholder parameters in the query. |
582 func (db *DB) Exec(query string, args ...interface{}) (Result, error) { | 646 func (db *DB) Exec(query string, args ...interface{}) (Result, error) { |
583 var res Result | 647 var res Result |
584 var err error | 648 var err error |
585 for i := 0; i < 10; i++ { | 649 for i := 0; i < 10; i++ { |
(...skipping 30 matching lines...) Expand all Loading... | |
616 } | 680 } |
617 } | 681 } |
618 | 682 |
619 dc.Lock() | 683 dc.Lock() |
620 si, err := dc.ci.Prepare(query) | 684 si, err := dc.ci.Prepare(query) |
621 dc.Unlock() | 685 dc.Unlock() |
622 if err != nil { | 686 if err != nil { |
623 return nil, err | 687 return nil, err |
624 } | 688 } |
625 defer withLock(dc, func() { si.Close() }) | 689 defer withLock(dc, func() { si.Close() }) |
626 | |
627 return resultFromStatement(driverStmt{dc, si}, args...) | 690 return resultFromStatement(driverStmt{dc, si}, args...) |
628 } | 691 } |
629 | 692 |
630 // Query executes a query that returns rows, typically a SELECT. | 693 // Query executes a query that returns rows, typically a SELECT. |
631 // The args are for any placeholder parameters in the query. | 694 // The args are for any placeholder parameters in the query. |
632 func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { | 695 func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { |
633 var rows *Rows | 696 var rows *Rows |
634 var err error | 697 var err error |
635 for i := 0; i < 10; i++ { | 698 for i := 0; i < 10; i++ { |
636 rows, err = db.query(query, args) | 699 rows, err = db.query(query, args) |
(...skipping 405 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1042 ci, err = s.tx.grabConn() // blocks, waiting for the connection. | 1105 ci, err = s.tx.grabConn() // blocks, waiting for the connection. |
1043 if err != nil { | 1106 if err != nil { |
1044 return | 1107 return |
1045 } | 1108 } |
1046 releaseConn = func(error) {} | 1109 releaseConn = func(error) {} |
1047 return ci, releaseConn, s.txsi.si, nil | 1110 return ci, releaseConn, s.txsi.si, nil |
1048 } | 1111 } |
1049 | 1112 |
1050 var cs connStmt | 1113 var cs connStmt |
1051 match := false | 1114 match := false |
1052 » for _, v := range s.css { | 1115 » for i := 0; i < len(s.css); i++ { |
1053 » » // TODO(bradfitz): lazily clean up entries in this | 1116 » » v := s.css[i] |
1054 » » // list with dead conns while enumerating | 1117 » » _, err := s.db.connIfFree(v.dc) |
1055 » » if _, match = s.db.connIfFree(v.dc); match { | 1118 » » if err == nil { |
1119 » » » match = true | |
1056 cs = v | 1120 cs = v |
1057 break | 1121 break |
1058 } | 1122 } |
1123 if err == errConnClosed { | |
1124 // Lazily remove dead conn from our freelist. | |
1125 s.css[i] = s.css[len(s.css)-1] | |
1126 s.css = s.css[:len(s.css)-1] | |
1127 i-- | |
1128 } | |
1129 | |
1059 } | 1130 } |
1060 s.mu.Unlock() | 1131 s.mu.Unlock() |
1061 | 1132 |
1062 // Make a new conn if all are busy. | 1133 // Make a new conn if all are busy. |
1063 // TODO(bradfitz): or wait for one? make configurable later? | 1134 // TODO(bradfitz): or wait for one? make configurable later? |
1064 if !match { | 1135 if !match { |
1065 for i := 0; ; i++ { | 1136 for i := 0; ; i++ { |
1066 dc, err := s.db.conn() | 1137 dc, err := s.db.conn() |
1067 if err != nil { | 1138 if err != nil { |
1068 return nil, nil, nil, err | 1139 return nil, nil, nil, err |
1069 } | 1140 } |
1070 dc.Lock() | 1141 dc.Lock() |
1071 » » » si, err := dc.ci.Prepare(s.query) | 1142 » » » si, err := dc.prepareLocked(s.query) |
1072 dc.Unlock() | 1143 dc.Unlock() |
1073 if err == driver.ErrBadConn && i < 10 { | 1144 if err == driver.ErrBadConn && i < 10 { |
1074 continue | 1145 continue |
1075 } | 1146 } |
1076 if err != nil { | 1147 if err != nil { |
1077 return nil, nil, nil, err | 1148 return nil, nil, nil, err |
1078 } | 1149 } |
1079 s.db.addDep(dc, s) | |
1080 s.mu.Lock() | 1150 s.mu.Lock() |
1081 cs = connStmt{dc, si} | 1151 cs = connStmt{dc, si} |
1082 s.css = append(s.css, cs) | 1152 s.css = append(s.css, cs) |
1083 s.mu.Unlock() | 1153 s.mu.Unlock() |
1084 break | 1154 break |
1085 } | 1155 } |
1086 } | 1156 } |
1087 | 1157 |
1088 conn := cs.dc | 1158 conn := cs.dc |
1089 releaseConn = func(err error) { s.db.putConn(conn, err) } | 1159 releaseConn = func(err error) { s.db.putConn(conn, err) } |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1188 s.txsi.Close() | 1258 s.txsi.Close() |
1189 return nil | 1259 return nil |
1190 } | 1260 } |
1191 | 1261 |
1192 return s.db.removeDep(s, s) | 1262 return s.db.removeDep(s, s) |
1193 } | 1263 } |
1194 | 1264 |
1195 func (s *Stmt) finalClose() error { | 1265 func (s *Stmt) finalClose() error { |
1196 for _, v := range s.css { | 1266 for _, v := range s.css { |
1197 s.db.noteUnusedDriverStatement(v.dc, v.si) | 1267 s.db.noteUnusedDriverStatement(v.dc, v.si) |
1268 v.dc.removeOpenStmt(v.si) | |
1198 s.db.removeDep(v.dc, s) | 1269 s.db.removeDep(v.dc, s) |
1199 } | 1270 } |
1200 s.css = nil | 1271 s.css = nil |
1201 return nil | 1272 return nil |
1202 } | 1273 } |
1203 | 1274 |
1204 // Rows is the result of a query. Its cursor starts before the first row | 1275 // Rows is the result of a query. Its cursor starts before the first row |
1205 // of the result set. Use Next to advance through the rows: | 1276 // of the result set. Use Next to advance through the rows: |
1206 // | 1277 // |
1207 // rows, err := db.Query("SELECT ...") | 1278 // rows, err := db.Query("SELECT ...") |
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1382 return dr.resi.LastInsertId() | 1453 return dr.resi.LastInsertId() |
1383 } | 1454 } |
1384 | 1455 |
1385 func (dr driverResult) RowsAffected() (int64, error) { | 1456 func (dr driverResult) RowsAffected() (int64, error) { |
1386 dr.Lock() | 1457 dr.Lock() |
1387 defer dr.Unlock() | 1458 defer dr.Unlock() |
1388 return dr.resi.RowsAffected() | 1459 return dr.resi.RowsAffected() |
1389 } | 1460 } |
1390 | 1461 |
1391 func stack() string { | 1462 func stack() string { |
1392 » var buf [1024]byte | 1463 » var buf [2 << 10]byte |
1393 return string(buf[:runtime.Stack(buf[:], false)]) | 1464 return string(buf[:runtime.Stack(buf[:], false)]) |
1394 } | 1465 } |
1395 | 1466 |
1396 // withLock runs while holding lk. | 1467 // withLock runs while holding lk. |
1397 func withLock(lk sync.Locker, fn func()) { | 1468 func withLock(lk sync.Locker, fn func()) { |
1398 lk.Lock() | 1469 lk.Lock() |
1399 fn() | 1470 fn() |
1400 lk.Unlock() | 1471 lk.Unlock() |
1401 } | 1472 } |
OLD | NEW |