2010-03-23 21:48:23 -06:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Malloc profiling.
|
|
|
|
// Patterned after tcmalloc's algorithms; shorter code.
|
|
|
|
|
|
|
|
package runtime
|
|
|
|
#include "runtime.h"
|
2011-12-16 13:33:58 -07:00
|
|
|
#include "arch_GOARCH.h"
|
2010-03-23 21:48:23 -06:00
|
|
|
#include "malloc.h"
|
2011-12-16 13:33:58 -07:00
|
|
|
#include "defs_GOOS_GOARCH.h"
|
2010-03-23 21:48:23 -06:00
|
|
|
#include "type.h"
|
|
|
|
|
|
|
|
// NOTE(rsc): Everything here could use cas if contention became an issue.
|
|
|
|
static Lock proflock;
|
|
|
|
|
|
|
|
// Per-call-stack allocation information.
|
|
|
|
// Lookup by hashing call stack into a linked-list hash table.
|
|
|
|
typedef struct Bucket Bucket;
|
|
|
|
struct Bucket
|
|
|
|
{
|
|
|
|
Bucket *next; // next in hash list
|
|
|
|
Bucket *allnext; // next in list of all buckets
|
|
|
|
uintptr allocs;
|
|
|
|
uintptr frees;
|
|
|
|
uintptr alloc_bytes;
|
|
|
|
uintptr free_bytes;
|
|
|
|
uintptr hash;
|
|
|
|
uintptr nstk;
|
|
|
|
uintptr stk[1];
|
|
|
|
};
|
|
|
|
enum {
|
|
|
|
BuckHashSize = 179999,
|
|
|
|
};
|
|
|
|
static Bucket **buckhash;
|
|
|
|
static Bucket *buckets;
|
|
|
|
static uintptr bucketmem;
|
|
|
|
|
|
|
|
// Return the bucket for stk[0:nstk], allocating new bucket if needed.
|
|
|
|
static Bucket*
|
|
|
|
stkbucket(uintptr *stk, int32 nstk)
|
|
|
|
{
|
|
|
|
int32 i;
|
|
|
|
uintptr h;
|
|
|
|
Bucket *b;
|
|
|
|
|
2010-03-29 18:30:07 -06:00
|
|
|
if(buckhash == nil) {
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
buckhash = runtime·SysAlloc(BuckHashSize*sizeof buckhash[0]);
|
2010-03-29 18:30:07 -06:00
|
|
|
mstats.buckhash_sys += BuckHashSize*sizeof buckhash[0];
|
|
|
|
}
|
2010-03-23 21:48:23 -06:00
|
|
|
|
|
|
|
// Hash stack.
|
|
|
|
h = 0;
|
|
|
|
for(i=0; i<nstk; i++) {
|
|
|
|
h += stk[i];
|
|
|
|
h += h<<10;
|
|
|
|
h ^= h>>6;
|
|
|
|
}
|
|
|
|
h += h<<3;
|
|
|
|
h ^= h>>11;
|
2010-03-24 10:40:09 -06:00
|
|
|
|
2010-03-23 21:48:23 -06:00
|
|
|
i = h%BuckHashSize;
|
|
|
|
for(b = buckhash[i]; b; b=b->next)
|
|
|
|
if(b->hash == h && b->nstk == nstk &&
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·mcmp((byte*)b->stk, (byte*)stk, nstk*sizeof stk[0]) == 0)
|
2010-03-23 21:48:23 -06:00
|
|
|
return b;
|
|
|
|
|
2011-02-02 21:03:47 -07:00
|
|
|
b = runtime·mallocgc(sizeof *b + nstk*sizeof stk[0], FlagNoProfiling, 0, 1);
|
2010-03-23 21:48:23 -06:00
|
|
|
bucketmem += sizeof *b + nstk*sizeof stk[0];
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·memmove(b->stk, stk, nstk*sizeof stk[0]);
|
2010-03-23 21:48:23 -06:00
|
|
|
b->hash = h;
|
|
|
|
b->nstk = nstk;
|
|
|
|
b->next = buckhash[i];
|
|
|
|
buckhash[i] = b;
|
|
|
|
b->allnext = buckets;
|
|
|
|
buckets = b;
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map from pointer to Bucket* that allocated it.
|
|
|
|
// Three levels:
|
|
|
|
// Linked-list hash table for top N-20 bits.
|
|
|
|
// Array index for next 13 bits.
|
|
|
|
// Linked list for next 7 bits.
|
|
|
|
// This is more efficient than using a general map,
|
|
|
|
// because of the typical clustering of the pointer keys.
|
|
|
|
|
|
|
|
typedef struct AddrHash AddrHash;
|
|
|
|
typedef struct AddrEntry AddrEntry;
|
|
|
|
|
|
|
|
struct AddrHash
|
|
|
|
{
|
|
|
|
AddrHash *next; // next in top-level hash table linked list
|
|
|
|
uintptr addr; // addr>>20
|
|
|
|
AddrEntry *dense[1<<13];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct AddrEntry
|
|
|
|
{
|
|
|
|
AddrEntry *next; // next in bottom-level linked list
|
|
|
|
uint32 addr;
|
|
|
|
Bucket *b;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
AddrHashBits = 12 // 1MB per entry, so good for 4GB of used address space
|
|
|
|
};
|
|
|
|
static AddrHash *addrhash[1<<AddrHashBits];
|
|
|
|
static AddrEntry *addrfree;
|
|
|
|
static uintptr addrmem;
|
|
|
|
|
|
|
|
// Multiplicative hash function:
|
|
|
|
// hashMultiplier is the bottom 32 bits of int((sqrt(5)-1)/2 * (1<<32)).
|
|
|
|
// This is a good multiplier as suggested in CLR, Knuth. The hash
|
|
|
|
// value is taken to be the top AddrHashBits bits of the bottom 32 bits
|
2011-05-30 02:02:59 -06:00
|
|
|
// of the multiplied value.
|
2010-03-23 21:48:23 -06:00
|
|
|
enum {
|
|
|
|
HashMultiplier = 2654435769U
|
|
|
|
};
|
|
|
|
|
|
|
|
// Set the bucket associated with addr to b.
|
|
|
|
static void
|
|
|
|
setaddrbucket(uintptr addr, Bucket *b)
|
|
|
|
{
|
|
|
|
int32 i;
|
|
|
|
uint32 h;
|
|
|
|
AddrHash *ah;
|
|
|
|
AddrEntry *e;
|
|
|
|
|
|
|
|
h = (uint32)((addr>>20)*HashMultiplier) >> (32-AddrHashBits);
|
|
|
|
for(ah=addrhash[h]; ah; ah=ah->next)
|
|
|
|
if(ah->addr == (addr>>20))
|
|
|
|
goto found;
|
|
|
|
|
2011-02-02 21:03:47 -07:00
|
|
|
ah = runtime·mallocgc(sizeof *ah, FlagNoProfiling, 0, 1);
|
2010-03-23 21:48:23 -06:00
|
|
|
addrmem += sizeof *ah;
|
|
|
|
ah->next = addrhash[h];
|
|
|
|
ah->addr = addr>>20;
|
|
|
|
addrhash[h] = ah;
|
|
|
|
|
|
|
|
found:
|
|
|
|
if((e = addrfree) == nil) {
|
2011-02-02 21:03:47 -07:00
|
|
|
e = runtime·mallocgc(64*sizeof *e, FlagNoProfiling, 0, 0);
|
2010-03-23 21:48:23 -06:00
|
|
|
addrmem += 64*sizeof *e;
|
|
|
|
for(i=0; i+1<64; i++)
|
|
|
|
e[i].next = &e[i+1];
|
|
|
|
e[63].next = nil;
|
|
|
|
}
|
|
|
|
addrfree = e->next;
|
|
|
|
e->addr = (uint32)~(addr & ((1<<20)-1));
|
|
|
|
e->b = b;
|
|
|
|
h = (addr>>7)&(nelem(ah->dense)-1); // entry in dense is top 13 bits of low 20.
|
|
|
|
e->next = ah->dense[h];
|
|
|
|
ah->dense[h] = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the bucket associated with addr and clear the association.
|
|
|
|
static Bucket*
|
|
|
|
getaddrbucket(uintptr addr)
|
|
|
|
{
|
|
|
|
uint32 h;
|
|
|
|
AddrHash *ah;
|
|
|
|
AddrEntry *e, **l;
|
|
|
|
Bucket *b;
|
2010-03-24 10:40:09 -06:00
|
|
|
|
2010-03-23 21:48:23 -06:00
|
|
|
h = (uint32)((addr>>20)*HashMultiplier) >> (32-AddrHashBits);
|
|
|
|
for(ah=addrhash[h]; ah; ah=ah->next)
|
|
|
|
if(ah->addr == (addr>>20))
|
|
|
|
goto found;
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
found:
|
|
|
|
h = (addr>>7)&(nelem(ah->dense)-1); // entry in dense is top 13 bits of low 20.
|
|
|
|
for(l=&ah->dense[h]; (e=*l) != nil; l=&e->next) {
|
|
|
|
if(e->addr == (uint32)~(addr & ((1<<20)-1))) {
|
|
|
|
*l = e->next;
|
|
|
|
b = e->b;
|
|
|
|
e->next = addrfree;
|
|
|
|
addrfree = e;
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by malloc to record a profiled block.
|
|
|
|
void
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·MProf_Malloc(void *p, uintptr size)
|
2010-03-23 21:48:23 -06:00
|
|
|
{
|
|
|
|
int32 nstk;
|
|
|
|
uintptr stk[32];
|
|
|
|
Bucket *b;
|
|
|
|
|
2010-03-24 10:40:09 -06:00
|
|
|
if(m->nomemprof > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m->nomemprof++;
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
nstk = runtime·callers(1, stk, 32);
|
|
|
|
runtime·lock(&proflock);
|
2010-03-23 21:48:23 -06:00
|
|
|
b = stkbucket(stk, nstk);
|
|
|
|
b->allocs++;
|
|
|
|
b->alloc_bytes += size;
|
|
|
|
setaddrbucket((uintptr)p, b);
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·unlock(&proflock);
|
2010-03-24 10:40:09 -06:00
|
|
|
m->nomemprof--;
|
2010-03-23 21:48:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called when freeing a profiled block.
|
|
|
|
void
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·MProf_Free(void *p, uintptr size)
|
2010-03-23 21:48:23 -06:00
|
|
|
{
|
|
|
|
Bucket *b;
|
|
|
|
|
2010-03-24 10:40:09 -06:00
|
|
|
if(m->nomemprof > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m->nomemprof++;
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·lock(&proflock);
|
2010-03-23 21:48:23 -06:00
|
|
|
b = getaddrbucket((uintptr)p);
|
|
|
|
if(b != nil) {
|
|
|
|
b->frees++;
|
|
|
|
b->free_bytes += size;
|
|
|
|
}
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·unlock(&proflock);
|
2010-03-24 10:40:09 -06:00
|
|
|
m->nomemprof--;
|
2010-03-23 21:48:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Go interface to profile data. (Declared in extern.go)
|
|
|
|
// Assumes Go sizeof(int) == sizeof(int32)
|
|
|
|
|
2010-03-24 10:40:09 -06:00
|
|
|
// Must match MemProfileRecord in extern.go.
|
|
|
|
typedef struct Record Record;
|
|
|
|
struct Record {
|
|
|
|
int64 alloc_bytes, free_bytes;
|
|
|
|
int64 alloc_objects, free_objects;
|
|
|
|
uintptr stk[32];
|
|
|
|
};
|
|
|
|
|
|
|
|
// Write b's data to r.
|
|
|
|
static void
|
|
|
|
record(Record *r, Bucket *b)
|
|
|
|
{
|
|
|
|
int32 i;
|
|
|
|
|
|
|
|
r->alloc_bytes = b->alloc_bytes;
|
|
|
|
r->free_bytes = b->free_bytes;
|
|
|
|
r->alloc_objects = b->allocs;
|
|
|
|
r->free_objects = b->frees;
|
|
|
|
for(i=0; i<b->nstk && i<nelem(r->stk); i++)
|
|
|
|
r->stk[i] = b->stk[i];
|
|
|
|
for(; i<nelem(r->stk); i++)
|
|
|
|
r->stk[i] = 0;
|
2010-03-23 21:48:23 -06:00
|
|
|
}
|
|
|
|
|
2010-03-24 10:40:09 -06:00
|
|
|
func MemProfile(p Slice, include_inuse_zero bool) (n int32, ok bool) {
|
|
|
|
Bucket *b;
|
|
|
|
Record *r;
|
|
|
|
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·lock(&proflock);
|
2010-03-24 10:40:09 -06:00
|
|
|
n = 0;
|
|
|
|
for(b=buckets; b; b=b->allnext)
|
|
|
|
if(include_inuse_zero || b->alloc_bytes != b->free_bytes)
|
|
|
|
n++;
|
|
|
|
ok = false;
|
|
|
|
if(n <= p.len) {
|
|
|
|
ok = true;
|
|
|
|
r = (Record*)p.array;
|
|
|
|
for(b=buckets; b; b=b->allnext)
|
|
|
|
if(include_inuse_zero || b->alloc_bytes != b->free_bytes)
|
|
|
|
record(r++, b);
|
|
|
|
}
|
runtime: ,s/[a-zA-Z0-9_]+/runtime·&/g, almost
Prefix all external symbols in runtime by runtime·,
to avoid conflicts with possible symbols of the same
name in linked-in C libraries. The obvious conflicts
are printf, malloc, and free, but hide everything to
avoid future pain.
The symbols left alone are:
** known to cgo **
_cgo_free
_cgo_malloc
libcgo_thread_start
initcgo
ncgocall
** known to linker **
_rt0_$GOARCH
_rt0_$GOARCH_$GOOS
text
etext
data
end
pclntab
epclntab
symtab
esymtab
** known to C compiler **
_divv
_modv
_div64by32
etc (arch specific)
Tested on darwin/386, darwin/amd64, linux/386, linux/amd64.
Built (but not tested) for freebsd/386, freebsd/amd64, linux/arm, windows/386.
R=r, PeterGo
CC=golang-dev
https://golang.org/cl/2899041
2010-11-04 12:00:19 -06:00
|
|
|
runtime·unlock(&proflock);
|
2010-03-24 10:40:09 -06:00
|
|
|
}
|