parsav  Check-in [7c8769bf96]

Overview
Comment:begin replacing inefficient memory management with a pool-based solution; fix memory leaks
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 7c8769bf968cda064fdda9ccd9fcd551ce183b0da38c153d85c4e14dc4bd1dd3
User & Date: lexi on 2021-01-10 08:19:56
Other Links: manifest | tags
Context
2021-01-10
11:17
add follow notices check-in: 00a6815988 user: lexi tags: trunk
08:19
begin replacing inefficient memory management with a pool-based solution; fix memory leaks check-in: 7c8769bf96 user: lexi tags: trunk
03:54
add memory pool impl, handle various little details, add beginnings of mimelib check-in: 8d35307a7f user: lexi tags: trunk
Changes

Modified html.t from [f6f8bc0dcc] to [06c69e3651].

     1      1   -- vim: ft=terra
     2      2   local m={}
     3      3   local pstr = lib.mem.ptr(int8)
     4      4   
     5         -terra m.sanitize(txt: pstr, quo: bool)
            5  +terra m.sanitize(pool: &lib.mem.pool, txt: pstr, quo: bool)
     6      6   	if txt.ptr == nil then return pstr.null() end
     7      7   	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
     8         -	var a: lib.str.acc a:init(txt.ct*1.3)
            8  +	var a: lib.str.acc a:pool(pool,txt.ct*1.3)
     9      9   	for i=0,txt.ct do
    10     10   		if txt(i) == @'<' then a:lpush('&lt;')
    11     11   		elseif txt(i) == @'>' then a:lpush('&gt;')
    12     12   		elseif txt(i) == @'&' then a:lpush('&amp;')
    13     13   		elseif quo and txt(i) == @'"' then a:lpush('&quot;')
    14     14   		else a:push(&txt(i),1) end
    15     15   	end
................................................................................
    24     24   
    25     25   terra m.hexbyte(i: uint8): int8[2]
    26     26   	return arrayof(int8,
    27     27   		m.hexdgt(i / 0x10),
    28     28   		m.hexdgt(i % 0x10))
    29     29   end
    30     30   
    31         -terra m.urlenc(txt: pstr, qparam: bool)
           31  +terra m.urlenc(pool: &lib.mem.pool, txt: pstr, qparam: bool)
    32     32   	if txt.ptr == nil then return pstr.null() end
    33     33   	if txt.ct == 0 then txt.ct = lib.str.sz(txt.ptr) end
    34         -	var a: lib.str.acc a:init(txt.ct*1.3)
           34  +	var a: lib.str.acc a:pool(pool,txt.ct*1.3)
    35     35   	for i=0,txt.ct do
    36     36   		if txt(i) == @' ' then a:lpush('+')
    37     37   		elseif txt(i) == @'&' and not qparam then a:lpush('&amp;')
    38     38   		elseif (txt(i) < 0x2c or
    39     39   			(txt(i) > @';'  and txt(i) < @'@') or
    40     40   			(txt(i) > @'Z'  and txt(i) < @'a') or
    41     41   			(txt(i) >= 0x7b and txt(i) <= 0x7f)) and

Modified mem.t from [de24aef2de] to [7e2b478eaf].

   179    179   end)
   180    180   
   181    181   struct m.pool {
   182    182    -- implements growable memory pools. EVERY THREAD MUST HAVE ITS OWN
   183    183   	storage: &opaque
   184    184   	cursor: &opaque
   185    185   	sz: intptr
          186  +	debris: &m.pool
   186    187   }
   187    188   
   188    189   terra m.pool:cue(sz: intptr)
   189    190   	if self.storage == nil then
   190    191   		self.storage = m.heapa_raw(sz)
   191    192   		self.cursor = self.storage
   192    193   		self.sz = sz
   193    194   	else
   194    195   		if self.sz >= sz then return self end
   195         -		var ofs = [&uint8](self.cursor) - [&uint8](self.storage)
   196         -		self.storage = m.heapr_raw(self.storage, sz)
   197         -		self.cursor = [&opaque]([&uint8](self.storage) + ofs)
   198         -		self.sz = sz
          196  +		var oldblock = @self
          197  +		self:init(sz)
          198  +		@self.debris = oldblock
   199    199   	end
   200    200   	return self
   201    201   end
   202    202   
   203    203   terra m.pool:init(sz: intptr)
   204         -	self.storage = nil
   205         -	self:cue(sz)
          204  +	var b = m.heapa_raw(sz + sizeof(m.pool))
          205  +	self.storage = [&uint8](b) + sizeof(m.pool)
          206  +	self.cursor = self.storage
          207  +	self.sz = sz
          208  +	self.debris = [&m.pool](b)
          209  +	self.debris.storage = nil
   206    210   	return self
   207    211   end
   208    212   
   209         -terra m.pool:free()
   210         -	m.heapf(self.storage)
          213  +terra m.pool:free(): {}
          214  +lib.io.fmt('DRAINING POOL %p\n',self.storage)
          215  +	if self.storage == nil then return end
          216  +	if self.debris.storage ~= nil then self.debris:free() end
          217  +	m.heapf(self.debris) -- storage + debris field allocated in one block
   211    218   	self.storage = nil
   212    219   	self.cursor = nil
   213    220   	self.sz = 0
          221  +	self.debris = nil
   214    222   end
   215    223   
   216    224   terra m.pool:clear()
          225  +	if self.debris.storage ~= nil then self.debris:free() end
   217    226   	self.cursor = self.storage
   218    227   	return self
   219    228   end
   220    229   
   221    230   terra m.pool:alloc_bytes(sz: intptr): &opaque
   222    231   	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
   223         -	if space < sz then self:cue(space + sz + 256) end
          232  +lib.io.fmt('%p / %p @ allocating %llu bytes in %llu of space\n',self.storage,self.cursor,sz,space)
          233  +	if space < sz then
          234  +lib.dbg('reserving more space')
          235  +		self:cue(space + sz + 256) end
   224    236   	var ptr = self.cursor
   225    237   	self.cursor = [&opaque]([&uint8](self.cursor) + sz)
   226    238   	return ptr
   227    239   end
   228    240   
   229         -m.pool.methods.alloc = macro(function(self,ty,sz)
   230         -	return `[ty](self:alloc_bytes(sizeof(ty) * sz))
          241  +terra m.pool:realloc_bytes(oldptr: &opaque, oldsz: intptr, newsz: intptr): &opaque
          242  +	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
          243  +	var cur = [&uint8](self.cursor)
          244  +	if cur - [&uint8](oldptr) == oldsz and newsz - oldsz < space then
          245  +		lib.dbg('moving pool cursor')
          246  +		cur = cur + (newsz - oldsz)
          247  +		self.cursor = [&opaque](cur)
          248  +		return oldptr
          249  +	else
          250  +		lib.dbg('copying pool object')
          251  +		var new = self:alloc_bytes(newsz)
          252  +		m.cpy(new, oldptr, oldsz)
          253  +		return new
          254  +	end
          255  +end
          256  +
          257  +m.pool.methods.alloc = macro(function(self,typ,sz)
          258  + 	local ty = typ:astype()
          259  +	return `[m.ptr(ty)] {
          260  +		ptr = [&ty](self:alloc_bytes(sizeof([ty]) * [sz]));
          261  +		ct = [sz];
          262  +	}
          263  +end)
          264  +
          265  +m.pool.methods.realloc = macro(function(self,ptr,oldsz,newsz)
          266  +	local ty = self.tree.type.type
          267  +	return `[m.ptr(ty)] {
          268  +		ptr = [&ty](self:realloc_bytes(ptr,
          269  +			sizeof(ty) * oldsz,
          270  +			sizeof(ty) * newsz));
          271  +		ct = sz;
          272  +	}
   231    273   end)
   232    274   
   233    275   terra m.pool:frame() -- stack-style linear mgmt
   234    276   	return self.cursor
   235    277   end
   236    278   
   237    279   terra m.pool:reset(frame: &opaque)
   238         -	self.cursor = frame
          280  +	if frame >= self.storage and frame <= self.cursor then
          281  +		self.cursor = frame
          282  +	else -- trying to rewind into a previous block! not possible
          283  +		self.cursor = self.storage
          284  +	end
   239    285   	return self
   240    286   end
   241    287   
   242    288   
   243    289   return m

Modified render/compose.t from [13509724e6] to [95dc7dcbc1].

    12     12   		form.acl = lib.trn(target == nil, 'all', 'mentioned') -- TODO default acl setting?
    13     13   	else
    14     14   		form.content = lib.coalesce(edit.body, '')
    15     15   		form.acl = edit.acl
    16     16   	end
    17     17   	if acc ~= nil then form:append(acc) return end 
    18     18   
    19         -	var cotxt = form:tostr() defer cotxt:free()
           19  +	var cotxt = form:poolstr(&co.srv.pool) -- defer cotxt:free()
    20     20   
    21     21   	var doc = [lib.srv.convo.page] {
    22     22   		title = lib.str.plit 'compose';
    23     23   		body = cotxt;
    24     24   		class = lib.str.plit 'compose';
    25     25   		cache = true;
    26     26   	}
    27     27   
    28     28   	co:stdpage(doc)
    29     29   end
    30     30   
    31     31   return render_compose

Modified render/conf.t from [241a1c4277] to [cd79efba6f].

    28     28   local invoker = quote co:complain(404,'not found','no such control panel is available in this version of parsav') end
    29     29   
    30     30   for i, m in ipairs(mappings) do
    31     31   	if lib.render.conf[m.render] then
    32     32   		invoker = quote
    33     33   			if path(1):cmp(lib.str.lit([m.url])) then
    34     34   				var body = [lib.render.conf[m.render]] (co, path)
    35         -				var a: lib.str.acc a:init(body.ct+48)
           35  +				var a = co:stra(body.ct+48)
    36     36   				if not body then
    37     37   					a:lpush(['<h1>' .. m.title .. ' :: error</h1>' ..
    38     38   						'<p>the requested resource is not available.</p>'])
    39     39   					panel = a:finalize()
    40     40   				else
    41     41   					a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body)
    42     42   					panel = a:finalize()
    43         -					body:free()
           43  +					--body:free()
    44     44   				end
    45     45   			else [invoker] end
    46     46   		end
    47     47   	end
    48     48   end
    49     49   
    50     50   local terra 
    51     51   render_conf([co], [path], notify: pstr)
    52         -	var menu: lib.str.acc menu:init(64):lpush('<hr>') defer menu:free()
           52  +	var menu = co:stra(256)
           53  +	menu:lpush('<hr>') 
    53     54   
    54     55   	-- build menu
    55     56   	do var p = co.who.rights.powers
    56     57   		if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end
    57     58   		if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end
    58     59   		if p.config() then menu:lpush([
    59     60   			'<a href="/conf/srv">server &amp; policy</a>' ..
................................................................................
    74     75   		menu = mptr;
    75     76   		panel = panel;
    76     77   	}
    77     78   
    78     79   	var pgt: pstr
    79     80   	if notify:ref() then
    80     81   		var fnpg: lib.str.acc
    81         -		fnpg:compose('<div class="flashmsg">', notify, '</div>')
           82  +		fnpg:pcompose(&co.srv.pool, '<div class="flashmsg">', notify, '</div>')
    82     83   		pg:append(&fnpg)
    83     84   		pgt = fnpg:finalize()
    84         -	else pgt = pg:tostr() end
    85         -	defer pgt:free()
           85  +	else pgt = pg:poolstr(&co.srv.pool) end
           86  +	--defer pgt:free()
    86     87   
    87     88   	co:stdpage([lib.srv.convo.page] {
    88     89   		title = 'configure'; body = pgt;
    89     90   		class = lib.str.plit 'conf';
    90     91   		cache = false;
    91     92   	})
    92     93   
    93         -	if panel.ct ~= 0 then panel:free() end
           94  +	--if panel.ct ~= 0 then panel:free() end
    94     95   end
    95     96   
    96     97   return render_conf

Modified render/conf/profile.t from [864a63a85e] to [5b3736f2f4].

    11     11   	var hue: int8[21]
    12     12   	var c = data.view.conf_profile {
    13     13   		handle = cs(co.who.handle);
    14     14   		nym = cs(lib.coalesce(co.who.nym,''));
    15     15   		bio = cs(lib.coalesce(co.who.bio,''));
    16     16   		hue = lib.math.decstr(co.ui_hue, &hue[20]);
    17     17   	}
    18         -	return c:tostr()
           18  +	return c:poolstr(&co.srv.pool)
    19     19   end
    20     20   
    21     21   return render_conf_profile

Modified render/conf/sec.t from [f6e2d18341] to [d16c1b9a13].

     9      9   	lib.osclock.ctime_r(&time, &tstr[0])
    10     10   	var body = data.view.conf_sec {
    11     11   		lastreset = pstr {
    12     12   			ptr = &tstr[0], ct = lib.str.sz(&tstr[0])
    13     13   		}
    14     14   	}
    15     15   	
    16         -	var a: lib.str.acc a:init(768) defer a:free()
           16  +	var a = co:stra(768) -- defer a:free()
    17     17   
    18     18   	if co.srv.cfg.credmgd then
    19     19   		var new = co:pgetv('new')
    20     20   		if not new then
    21     21   			body:append(&a)
    22     22   			var credmgr = data.view.conf_sec_credmg {
    23     23   				credlist = pstr{'',0};
    24     24   			}
    25     25   			var creds = co.srv:auth_enum_uid(uid)
    26     26   			if creds.ct > 0 then defer creds:free()
    27         -				var cl: lib.str.acc cl:init(256)
           27  +				var cl = co:stra(256)
    28     28   				for i=0, creds.ct do var c = creds(i).ptr
    29     29   					if not c.blacklist then
    30     30   						cl:lpush('<option value="'):shpush(c.aid):lpush('"> ['):push(c.kind,0):lpush('] '):push(c.comment,0)
    31     31   						if c.netmask.pv ~= 0 then
    32     32   							-- push string rep
    33     33   						end
    34     34   						cl:lpush('</option>')
    35     35   					end
    36     36   				end
    37     37   				credmgr.credlist = cl:finalize()
    38     38   			end
    39     39   			credmgr:append(&a)
    40         -			if credmgr.credlist.ct > 0 then credmgr.credlist:free() end
           40  +			--if credmgr.credlist.ct > 0 then credmgr.credlist:free() end
    41     41   		else
    42     42   			if new:cmp(lib.str.plit'pw') then
    43     43   				var d: data.view.conf_sec_pwnew
    44     44   				var time = lib.osclock.time(nil)
    45     45   				var timestr: int8[26] lib.osclock.ctime_r(&time, &timestr[0])
    46         -				var cmt: lib.str.acc
    47         -				cmt:init(48):lpush('enrolled over http on '):push(&timestr[0],0)
           46  +				var cmt = co:stra(48)
           47  +				cmt:lpush('enrolled over http on '):push(&timestr[0],0)
    48     48   				d.comment = cmt:finalize()
    49     49   
    50         -				var st = d:tostr()
    51         -				d.comment:free()
           50  +				var st = d:poolstr(&co.srv.pool)
           51  +				--d.comment:free()
    52     52   				return st
    53     53   			elseif new:cmp(lib.str.plit'challenge') then
    54     54   			-- we're going to break the rules a bit and do database munging from
    55     55   			-- the rendering code, because doing otherwise in this case would be
    56     56   			-- genuinely nightmarish
    57     57   			elseif new:cmp(lib.str.plit'otp') then
    58     58   			elseif new:cmp(lib.str.plit'api') then

Modified render/conf/users.t from [59cb9f4fac] to [7c5c858095].

   170    170   		-- FIXME allow xids as well, for manual queries
   171    171   		if not user then goto e404 end
   172    172   		defer user:free()
   173    173   		if not co.who:overpowers(user.ptr) then goto e403 end
   174    174   
   175    175   		if path.ct == 4 then
   176    176   			if path(3):cmp(lib.str.lit'cred') then
   177         -				var pg: lib.str.acc pg:init(1024)
          177  +				var pg = co:stra(1024)
   178    178   				pg:lpush('<div class="context">editing credentials for user <a href="/conf/users/'):rpush(path(2)):lpush('">'):push(user(0).xid,0):lpush('</a></div>')
   179    179   				var credmgr = lib.render.conf.sec(co, uid)
   180    180   				pg:ppush(credmgr)
   181         -				credmgr:free()
          181  +				--credmgr:free()
   182    182   				return pg:finalize()
   183    183   			else goto e404 end
   184    184   		elseif path.ct == 3 then
   185         -			var cinp: lib.str.acc cinp:init(256)
          185  +			var cinp = co:stra(256)
   186    186   			cinp:lpush('<div class="elem-group">')
   187    187   			if user.ptr.rights.rank > 0 and (co.who.rights.powers.elevate() or co.who.rights.powers.demote()) then
   188    188   				var max = co.who.rights.rank
   189    189   				if not co.who.rights.powers.elevate() then max = user.ptr.rights.rank end
   190    190   				var min = co.srv.cfg.nranks
   191    191   				if not co.who.rights.powers.demote() then min = user.ptr.rights.rank end
   192    192   
   193    193   				push_num_field(cinp, 'rank', 'rank', max, min, user.ptr.rights.rank, user.ptr.id == co.who.id)
   194    194   			end
   195    195   			if co.who.rights.powers.herald() then
   196    196   				var sanitized: pstr
   197    197   				if user.ptr.epithet == nil
   198    198   					then sanitized = pstr {ptr='', ct=0}
   199         -					else sanitized = lib.html.sanitize(cs(user.ptr.epithet),true)
          199  +					else sanitized = lib.html.sanitize(&co.srv.pool,cs(user.ptr.epithet),true)
   200    200   				end
   201    201   				cinp:lpush('<div class="elem"><label for="epithet">epithet</label><input type="text" id="epithet" name="epithet" value="'):ppush(sanitized):lpush('"></div>')
   202         -				if user.ptr.epithet ~= nil then sanitized:free() end
          202  +				--if user.ptr.epithet ~= nil then sanitized:free() end
   203    203   			end
   204    204   			if co.who.rights.powers.invite() or co.who.rights.powers.discipline() then
   205    205   				var min: uint32 = 0
   206    206   				if not (co.who.rights.powers.discipline() or
   207    207   					co.who.rights.powers.demote() and co.who.rights.powers.invite())
   208    208   						then min = user.ptr.rights.invites end
   209    209   				var max: uint32 = co.srv.cfg.maxinvites
................................................................................
   234    234   				var map = array([lib.store.powmap])
   235    235   				cinp:lpush('<details><summary>powers</summary><div class="pick-list">')
   236    236   					for i=0, [map.type.N] do
   237    237   						if (co.who.rights.powers and map[i].val):sz() > 0 then
   238    238   							var on = (user.ptr.rights.powers and map[i].val):sz() > 0
   239    239   							var enabled = (     on  and co.who.rights.powers.demote() ) or
   240    240   										  ((not on) and co.who.rights.powers.elevate())
   241         -							var namea: lib.str.acc namea:compose('power-', map[i].name)
          241  +							var namea: lib.str.acc namea:pcompose(&co.srv.pool,'power-', map[i].name)
   242    242   							var name = namea:finalize()
   243    243   							push_pickbox(&cinp, name, pstr.null(), map[i].name, on, enabled, pstr.null())
   244         -							name:free()
          244  +							--name:free()
   245    245   						end
   246    246   					end
   247    247   				cinp:lpush('</div></details>')
   248    248   			end
   249    249   
   250    250   			if co.who.id ~= uid and co.who.rights.powers.purge() then
   251         -				var purgeconf: lib.str.acc purgeconf:init(48)
          251  +				var purgeconf = co:stra(48)
   252    252   				var purgestrs = array(
   253    253   					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
   254    254   					'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst', 'glory',
   255    255   					'hope', 'grace', 'pearl', 'carnation', 'rose', 'peony', 'poppy'
   256    256   				)
   257    257   				for i=0,3 do
   258    258   					purgeconf:push(purgestrs[lib.crypt.random(intptr,0,[purgestrs.type.N])],0)
   259    259   					if i ~= 2 then purgeconf:lpush('-') end
   260    260   				end
   261    261   				cinp:lpush('<details><summary>purge account</summary><p>you have the authority to destroy this account and all its associated content irreversibly and irretrievably. if you really wish to apply such an extreme sanction, enter the confirmation string <strong style="user-select:none">'):push(purgeconf.buf,purgeconf.sz):lpush('</strong> below and press the “alter” button to begin the process.</p><div class="elem"><label for="purge">purge confirmation string</label><input type="text" id="purge" name="purgekey"></div><input type="hidden" name="purgestr" value="'):push(purgeconf.buf,purgeconf.sz):lpush('"></details>')
   262         -				purgeconf:free()
          262  +				--purgeconf:free()
   263    263   			end
   264    264   
   265    265   			-- TODO black mark system? e.g. resolution option for badthink reports
   266    266   			-- adds a black mark to the offending user; they can be automatically banned
   267    267   			-- or brought up for review after a certain number of offenses; possibly lower
   268    268   			-- set of default privs for marked users
   269    269   
   270         -			var cinpp = cinp:finalize() defer cinpp:free()
   271         -			var unym: lib.str.acc unym:init(64)
          270  +			var cinpp = cinp:finalize() --defer cinpp:free()
          271  +			var unym = co:stra(64)
   272    272   			unym:lpush('<a href="/')
   273    273   			if user(0).origin ~= 0 then unym:lpush('@') end
   274         -			do var sanxid = lib.html.sanitize(user(0).xid, true)
          274  +			do var sanxid = lib.html.sanitize(&co.srv.pool,user(0).xid, true)
   275    275   				unym:ppush(sanxid)
   276         -				sanxid:free() end
          276  +				--sanxid:free()
          277  +			end
   277    278   			unym:lpush('" class="id">')
   278    279   			lib.render.nym(user.ptr,0,&unym,false)
   279    280   			unym:lpush('</a>')
   280    281   			var ctlbox = data.view.conf_user_ctl {
   281    282   				name = unym:finalize();
   282    283   				inputcontent = cinpp;
   283    284   				btns = pstr{'',0};
   284    285   			}
   285    286   			if co.who.id ~= uid and co.who.rights.powers.cred() then
   286         -				ctlbox.btns = lib.str.acc{}:compose('<a class="button" href="/conf/users/',path(2),'/cred">security &amp; credentials</a>'):finalize()
          287  +				ctlbox.btns = lib.str.acc{}:pcompose(&co.srv.pool,'<a class="button" href="/conf/users/',path(2),'/cred">security &amp; credentials</a>'):finalize()
   287    288   			end
   288         -			var pg: lib.str.acc pg:init(512)
          289  +			var pg = co:stra(512)
   289    290   			ctlbox:append(&pg)
   290         -			ctlbox.name:free()
   291         -			if ctlbox.btns.ct > 0 then ctlbox.btns:free() end
          291  +			--ctlbox.name:free()
          292  +			--if ctlbox.btns.ct > 0 then ctlbox.btns:free() end
   292    293   
   293    294   			return pg:finalize()
   294    295   		end
   295    296   	else
   296    297   		var modes = array(P'local', P'remote', P'staff', P'titled', P'peons', P'all')
   297    298   		var idbuf: int8[lib.math.shorthand.maxlen]
   298         -		var ulst: lib.str.acc ulst:init(256)
          299  +		var ulst = co:stra(256)
   299    300   		var mode: uint8 = mode_local
   300    301   		var modestr = co:pgetv('show')
   301    302   		ulst:lpush('<div style="text-align: right"><em>showing ')
   302    303   		for i=0,[modes.type.N] do
   303    304   			if modestr:ref() and modes[i]:cmp(modestr) then mode = i end
   304    305   		end
   305    306   		for i=0,[modes.type.N] do

Modified render/media-gallery.t from [da27a31f83] to [5c4e9df122].

    14     14   	var owner = false
    15     15   	if co.aid ~= 0 and co.who.id == uid then owner = true end
    16     16   	var ou = co.srv:actor_fetch_uid(uid)
    17     17   	if not ou then goto e404 end
    18     18   	do defer ou:free()
    19     19   		var pfx = pstr.null()
    20     20   		if not owner then
    21         -			var pa: lib.str.acc pa:init(32)
           21  +			var pa = co:stra(32)
    22     22   			pa:lpush('/')
    23     23   			if ou(0).origin ~= 0 then pa:lpush('@') end
    24     24   			pa:push(ou(0).xid,0)
    25     25   			pfx = pa:finalize()
    26     26   		end
    27     27   
    28     28   		if path.ct >= 3 and path(1):cmp(lib.str.lit'a') then
................................................................................
    31     31   			var art = co.srv:artifact_fetch(uid, id)
    32     32   			if not art then goto e404 end
    33     33   			if path.ct == 3 then
    34     34   			-- sniff out the artifact type and display the appropriate viewer
    35     35   				var artid = cs(art(0).url)
    36     36   				var btns: lib.str.acc
    37     37   				if owner then
    38         -					btns:compose('<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>')
           38  +					btns:pcompose(&co.srv.pool,'<a class="neg button" href="',pfx,'/media/a/',artid,'/del">delete</a><a class="button" href="',pfx,'/media/a/',artid,'/edit">alter</a>')
    39     39   				else
    40         -					btns:compose('<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>')
           40  +					btns:pcompose(&co.srv.pool,'<a class="pos button" href="',pfx,'/media/a/',artid,'/collect">collect</a>')
    41     41   				end
    42         -				var btntxt = btns:finalize() defer btntxt:free()
    43         -				var desc = lib.smackdown.html(pstr{art(0).desc,0}, true) defer desc:free()
           42  +				var btntxt = btns:finalize() -- defer btntxt:free()
           43  +				var desc = lib.smackdown.html(&co.srv.pool, pstr{art(0).desc,0}, true) -- defer desc:free()
    44     44   				var viewerprops = {
    45     45   					pfx = pfx, desc = desc;
    46     46   					id = artid; btns = btntxt;
    47     47   				}
    48     48   				if lib.str.ncmp(art(0).mime, 'image/', 6) == 0 then
    49     49   					var view = data.view.media_image(viewerprops)
    50         -					var pg = view:tostr()
           50  +					var pg = view:poolstr(&co.srv.pool)
    51     51   					co:stdpage([lib.srv.convo.page] {
    52     52   						title = lib.str.plit'media :: image';
    53     53   						class = lib.str.plit'media viewer img';
    54     54   						cache = false, body = pg;
    55     55   					})
    56         -					pg:free()
           56  +					--pg:free()
    57     57   				elseif lib.str.cmp(art(0).mime, 'text/markdown') == 0 then
    58     58   					var view = data.view.media_text(viewerprops)
    59     59   					var text, mime = co.srv:artifact_load(id) mime:free()
    60         -					view.text = lib.smackdown.html(pstr{[rawstring](text.ptr),text.ct}, false)
           60  +					view.text = lib.smackdown.html(&co.srv.pool, pstr{[rawstring](text.ptr),text.ct}, false)
    61     61   					text:free()
    62         -					var pg = view:tostr()
    63         -					view.text:free()
           62  +					var pg = view:poolstr(&co.srv.pool)
           63  +					--view.text:free()
    64     64   					co:stdpage([lib.srv.convo.page] {
    65     65   						title = lib.str.plit'media :: text';
    66     66   						class = lib.str.plit'media viewer text';
    67     67   						cache = false, body = pg;
    68     68   					})
    69         -					pg:free()
           69  +					--pg:free()
    70     70   				elseif
    71     71   					lib.str.ncmp(art(0).mime, 'text/', 5) == 0          or
    72     72   					lib.str.cmp(art(0).mime, 'application/x-perl') == 0 or
    73     73   					lib.str.cmp(art(0).mime, 'application/sql') == 0
    74     74   					-- and so on (we need a mimelib at some point) --
    75     75   				then
    76     76   					var view = data.view.media_text(viewerprops)
    77     77   					var text, mime = co.srv:artifact_load(id) mime:free()
    78         -					var san = lib.html.sanitize(pstr{[rawstring](text.ptr),text.ct}, false)
           78  +					var san = lib.html.sanitize(&co.srv.pool,pstr{[rawstring](text.ptr),text.ct}, false)
    79     79   					text:free()
    80     80   					view.text = lib.str.acc{}:compose('<pre>',san,'</pre>'):finalize()
    81     81   					san:free()
    82     82   					var pg = view:tostr()
    83     83   					view.text:free()
    84     84   					co:stdpage([lib.srv.convo.page] {
    85     85   						title = lib.str.plit'media :: text';
................................................................................
   123    123   				folders = pstr{'',0};
   124    124   				directory = pstr{'',0};
   125    125   				images = pstr{'',0};
   126    126   				pfx = pfx;
   127    127   			}
   128    128   
   129    129   			if folders.ct > 0 then
   130         -				var fa: lib.str.acc fa:init(128)
          130  +				var fa: lib.str.acc fa:pool(&co.srv.pool,128)
   131    131   				var fldr = co:pgetv('folder')
   132    132   				for i=0,folders.ct do
   133         -					var ule = lib.html.urlenc(folders(i), true) defer ule:free()
   134         -					var san = lib.html.sanitize(folders(i), true) defer san:free()
          133  +					var ule = lib.html.urlenc(&co.srv.pool,folders(i), true) -- defer ule:free()
          134  +					var san = lib.html.sanitize(&co.srv.pool,folders(i), true) -- defer san:free()
   135    135   					fa:lpush('<a href="'):ppush(pfx):lpush('/media?folder='):ppush(ule)
   136    136   						:lpush('">'):ppush(san):lpush('</a>')
   137    137   					lib.dbg('checking folder ',{fldr.ptr,fldr.ct},' against ',{folders(i).ptr,folders(i).ct})
   138    138   					if fldr:ref() and folders(i):cmp(fldr)
   139    139   						then folder = folders(i) lib.dbg('folder match ',{fldr.ptr,fldr.ct})
   140    140   						else folders(i):free()
   141    141   					end
................................................................................
   146    146   			end
   147    147   
   148    148   			if owner then
   149    149   				view.menu = P'<a class="pos" href="/media/upload">upload</a><hr>'
   150    150   			end
   151    151   
   152    152   			var md = co.srv:artifact_enum_uid(uid, folder)
   153         -			var gallery: lib.str.acc gallery:init(256)
   154         -			var files: lib.str.acc files:init(256) 
          153  +			var gallery: lib.str.acc gallery:pool(&co.srv.pool,256)
          154  +			var files: lib.str.acc files:pool(&co.srv.pool,256) 
   155    155   			for i=0,md.ct do
   156         -				var desc = lib.smackdown.html(pstr{md(i)(0).desc,0}, true) defer desc:free()
          156  +				var desc = lib.smackdown.html(&co.srv.pool,pstr{md(i)(0).desc,0}, true) --defer desc:free()
   157    157   				if lib.str.ncmp(md(i)(0).mime, 'image/', 6) == 0 then
   158    158   					gallery:lpush('<a class="thumb" href="'):ppush(pfx):lpush('/media/a/')
   159    159   						:push(md(i)(0).url,0):lpush('"><img src="/file/'):push(md(i)(0).url,0)
   160    160   						:lpush('"><div class="caption">'):ppush(desc)
   161    161   						:lpush('</div></a>')
   162    162   				else
   163         -					var mime = lib.html.sanitize(pstr{md(i)(0).mime,0}, true) defer mime:free() --just in case
          163  +					var mime = lib.html.sanitize(&co.srv.pool,pstr{md(i)(0).mime,0}, true) --defer mime:free() --just in case
   164    164   					files:lpush('<a class="file" href="'):ppush(pfx):lpush('/media/a/')
   165    165   						:push(md(i)(0).url,0):lpush('"><span class="label">'):ppush(desc)
   166    166   						:lpush('</span> <span class="mime">'):ppush(mime)
   167    167   						:lpush('</span></a>')
   168    168   				end
   169    169   				md(i):free()
   170    170   			end
................................................................................
   171    171   
   172    172   			view.images = gallery:finalize()
   173    173   			view.directory = files:finalize()
   174    174   
   175    175   			if acc ~= nil then
   176    176   				view:append(acc)
   177    177   			else
   178         -			lib.dbg('emitting page')
   179         -				var pg = view:tostr() defer pg:free()
   180         -			lib.dbg('compiled page')
          178  +				var pg = view:poolstr(&co.srv.pool) -- defer pg:free()
   181    179   				co:stdpage([lib.srv.convo.page] {
   182    180   					title = P'media';
   183    181   					class = P'media manager';
   184    182   					cache = false;
   185    183   					body = pg;
   186    184   				})
   187         -			lib.dbg('sent page')
   188    185   			end
   189    186   
   190         -			view.images:free()
   191         -			view.directory:free()
   192         -			if view.folders.ct > 0 then view.folders:free() end
          187  +			--view.images:free()
          188  +			--view.directory:free()
          189  +			--if view.folders.ct > 0 then view.folders:free() end
   193    190   			if folder.ct > 0 then folder:free() end
   194    191   			if md:ref() then md:free() end
   195    192   		end
   196         -		if not owner then pfx:free() end
          193  +		--if not owner then pfx:free() end
   197    194   	return end
   198    195   
   199    196   	::e404:: co:complain(404,'media not found','no such media exists on this server')
   200    197   end
   201    198   
   202    199   return render_media_gallery

Modified render/nav.t from [0fd87c81ae] to [9f2c55cf5b].

     1      1   -- vim: ft=terra
     2      2   local terra 
     3      3   render_nav(co: &lib.srv.convo)
     4         -	var t: lib.str.acc t:init(64)
            4  +	var t = co:stra(64)
     5      5   	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
     6      6   		t:lpush(' <a accesskey="t" href="/">timeline</a>')
     7      7   	end
     8      8   	if co.who ~= nil then
     9      9   		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
    10     10   		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
    11     11   		t:push(co.who.handle,0)

Modified render/notices.t from [99b348e685] to [745afb2f25].

    13     13   	
    14     14   	if notes.ct == 0 then
    15     15   		co:complain(200,'no news is good news',"you don't have any notices to review")
    16     16   		return
    17     17   	end
    18     18   	defer notes:free()
    19     19   
    20         -	var pg: lib.str.acc pg:init(512) defer pg:free()
    21         -	var pflink: lib.str.acc pflink:init(64)
    22         -	var body: lib.str.acc body:init(256)
           20  +	var pg = co:stra(512) -- defer pg:free()
           21  +	var pflink = co:stra(64)
           22  +	var body = co:stra(256)
    23     23   	var latest: lib.store.timepoint = 0
    24     24   	for i=0,notes.ct do
    25     25   		if notes(i).when > latest then latest = notes(i).when end
    26     26   		var who = co.srv:actor_fetch_uid(notes(i).who) defer who:free()
    27     27   		if not who then lib.bail('schema integrity violation: nonexistent actor referenced in notification, this is almost certainly an SQL error or bug in the backend implementation') end
    28     28   		pflink:cue(lib.str.sz(who(0).xid) + 4)
    29     29   		if who(0).origin == 0 then pflink:lpush('/')
................................................................................
    49     49   				n.kind = P'reply'
    50     50   				n.act = P'replied to your post'
    51     51   				notweet = false
    52     52   			end
    53     53   		else goto skip end
    54     54   		do var idbuf: int8[lib.math.shorthand.maxlen]
    55     55   			var idlen = lib.math.shorthand.gen(notes(i).what, idbuf)
    56         -			var b = lib.smackdown.html(pstr {ptr=what(0).body,ct=0},true) defer b:free()
           56  +			var b = lib.smackdown.html(&co.srv.pool, pstr {ptr=what(0).body,ct=0},true) --defer b:free()
    57     57   			body:lpush(' <a class="quote" href="/post/'):push(&idbuf[0],idlen):lpush('">'):ppush(b):lpush('</a>')
    58     58   		end
    59     59   		if not notweet then
    60     60   			var reply = co.srv:post_fetch(notes(i).reply)
    61     61   				lib.render.tweet(co,reply.ptr,&body)
    62     62   			reply:free()
    63     63   		end
................................................................................
    64     64   		n.ref = pstr {ptr = body.buf, ct = body.sz}
    65     65   
    66     66   		n:append(&pg)
    67     67   		::skip:: n.nym:free()
    68     68   		         pflink:reset()
    69     69   				 body:reset()
    70     70   	end
    71         -	pflink:free()
           71  +	--pflink:free()
    72     72   	pg:lpush('<form method="post"><button name="act" value="clear">clear all notices</button></form>')
    73     73   	co:livepage([lib.srv.convo.page] {
    74     74   		title = P'notices', class = P'notices';
    75     75   		body = pstr {ptr = pg.buf, ct = pg.sz};
    76     76   		cache = false;
    77     77   	}, latest)
    78     78   end
    79     79   
    80     80   return render_notices

Modified render/nym.t from [ea921b8ffe] to [c2ab22760a].

     8      8   render_nym(who: &lib.store.actor, scope: uint64, tgt: &lib.str.acc, minimal: bool)
     9      9   	var acc: lib.str.acc
    10     10   	var n: &lib.str.acc
    11     11   	if tgt ~= nil then n = tgt else
    12     12   		n = &acc
    13     13   		n:init(128)
    14     14   	end
    15         -	var xidsan = lib.html.sanitize(cs(who.xid),false)
           15  +
           16  +	var pool_obj: lib.mem.pool
           17  +	var pool: &lib.mem.pool
           18  +	if tgt ~= nil and tgt.pool ~= nil then
           19  +		pool = tgt.pool
           20  +	else
           21  +		pool_obj:init(128)
           22  +		pool = &pool_obj
           23  +	end
           24  +
           25  +	var xidsan = lib.html.sanitize(pool,cs(who.xid),false)
    16     26   	if who.nym ~= nil and who.nym[0] ~= 0 then
    17         -		var nymsan = lib.html.sanitize(cs(who.nym),false)
           27  +		var nymsan = lib.html.sanitize(pool,cs(who.nym),false)
    18     28   		n:lpush('<span class="nym">'):ppush(nymsan)
    19     29   			:lpush('</span> [<span class="handle">'):ppush(xidsan)
    20     30   			:lpush('</span>]')
    21         -		nymsan:free()
           31  +		--nymsan:free()
    22     32   	else n:lpush('<span class="handle">'):ppush(xidsan):lpush('</span>') end
    23         -	xidsan:free()
           33  +	--xidsan:free()
    24     34   
    25     35   	if not minimal then
    26     36   		if who.epithet ~= nil then
    27         -			var episan = lib.html.sanitize(cs(who.epithet),false)
           37  +			var episan = lib.html.sanitize(pool,cs(who.epithet),false)
    28     38   			n:lpush('<span class="epithet">'):ppush(episan):lpush('</span>')
    29         -			episan:free()
           39  +			--episan:free()
    30     40   		end
    31     41   	end
    32     42   	
           43  +	if pool == &pool_obj then pool:free() end
    33     44   	-- TODO: if scope == chat room then lookup titles in room member db
    34     45   	if tgt == nil then
    35     46   		return n:finalize()
    36     47   	else return pstr.null() end
    37     48   end
    38     49   
    39     50   return render_nym

Modified render/profile.t from [3525ca58bc] to [a033243372].

     9      9   	co: &lib.srv.convo,
    10     10   	actor: &lib.store.actor,
    11     11   	relationship: &lib.store.relationship
    12     12   ): pstr
    13     13   	var aux: lib.str.acc
    14     14   	var followed = false -- FIXME
    15     15   	if co.aid ~= 0 and co.who.id == actor.id then
    16         -		aux:compose('<a accesskey="a" class="button" href="/conf/profile?go=/@',actor.handle,'">alter</a>')
           16  +		aux:pcompose(&co.srv.pool,'<a accesskey="a" class="button" href="/conf/profile?go=/@',actor.handle,'">alter</a>')
    17     17   	elseif co.aid ~= 0 then
    18     18   		if not relationship.rel.follow() then
    19         -			aux:compose('<button accesskey="f" method="post" class="pos" name="act" value="follow">follow</button>')
           19  +			aux:pcompose(&co.srv.pool,'<button accesskey="f" method="post" class="pos" name="act" value="follow">follow</button>')
    20     20   		elseif relationship.rel.follow() then
    21         -			aux:compose('<button accesskey="f" method="post" class="neg" name="act" value="unfollow">unfollow</button>')
           21  +			aux:pcompose(&co.srv.pool,'<button accesskey="f" method="post" class="neg" name="act" value="unfollow">unfollow</button>')
    22     22   		end
    23     23   		aux:lpush('<a accesskey="h" class="button" href="/'):push(actor.xid,0):lpush('/chat">chat</a>')
    24     24   		if co.who.rights.powers:affect_users() and co.who:overpowers(actor) then
    25     25   			aux:lpush('<a accesskey="n" class="button" href="/'):push(actor.xid,0):lpush('/ctl">control</a>')
    26     26   		end
    27     27   	else
    28         -		aux:compose('<a accesskey="f" class="button" href="/', actor.xid, '/follow">remote follow</a>')
           28  +		aux:pcompose(&co.srv.pool,'<a accesskey="f" class="button" href="/', actor.xid, '/follow">remote follow</a>')
    29     29   	end
    30     30   	var auxp = aux:finalize()
    31     31   	var timestr: int8[26] lib.osclock.ctime_r(&actor.knownsince, &timestr[0])
    32     32   
    33     33   	var strfbuf: int8[28*4]
    34     34   	var stats = co.srv:actor_stats(actor.id)
    35     35   		var sn_posts     = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ]))
    36     36   		var sn_follows   = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1))
    37     37   		var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1))
    38     38   		var sn_mutuals   = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1))
    39     39   	var bio = lib.str.plit '<em style="opacity:0.6">tall, dark, and mysterious</em>'
    40     40   	if actor.bio ~= nil then
    41         -		bio = lib.smackdown.html(cs(actor.bio),false)
           41  +		bio = lib.smackdown.html(&co.srv.pool,cs(actor.bio),false)
    42     42   	end
    43     43   	var fullname = lib.render.nym(actor,0,nil,false) defer fullname:free()
    44         -	var comments: lib.str.acc comments:init(64)
           44  +	var comments: lib.str.acc comments:pool(&co.srv.pool,64)
    45     45   
    46     46   	if co.srv.cfg.master == actor.id then
    47     47   		var foundertxt = lib.str.plit 'founder'
    48     48   		if co.srv.cfg.ui_cue_founder:ref() then
    49     49   			if co.srv.cfg.ui_cue_founder.ct == 0 -- empty string, suppress field
    50     50   				then foundertxt = pstr.null()
    51     51   				else foundertxt = co.srv.cfg.ui_cue_founder
................................................................................
    94     94   
    95     95   		remarks = '';
    96     96   
    97     97   		auxbtn = auxp;
    98     98   	}
    99     99   	if comments.sz > 0 then profile.remarks = comments:finalize() end
   100    100   
   101         -	var ret = profile:tostr()
   102         -	auxp:free() 
   103         -	if actor.bio ~= nil then bio:free() end
          101  +	var ret = profile:poolstr(&co.srv.pool)
          102  +	-- auxp:free() 
          103  +	--if actor.bio ~= nil then bio:free() end
   104    104   	if comments.sz > 0 then profile.remarks:free() end
   105    105   	return ret
   106    106   end
   107    107   
   108    108   return render_profile

Modified render/timeline.t from [ab5808172b] to [7375f87c90].

    22     22   			from_time = stoptime;
    23     23   			to_idx = 64;
    24     24   		})
    25     25   	elseif mode == modes.fediglobal then
    26     26   	elseif mode == modes.circle then
    27     27   	end
    28     28   
    29         -	var acc: lib.str.acc acc:init(1024)
           29  +	var acc: lib.str.acc acc:pool(&co.srv.pool,1024)
    30     30   	acc:lpush('<div id="tl" data-live="10">')
    31     31   	var newest: lib.store.timepoint = 0
    32     32   	for i = 0, posts.sz do
    33     33   		lib.render.tweet(co, posts(i).ptr, &acc)
    34     34   		var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited)
    35     35   		if t > newest then newest = t end
    36     36   		posts(i):free()
................................................................................
    41     41   	var doc = [lib.srv.convo.page] {
    42     42   		title = lib.str.plit'timeline';
    43     43   		body = acc:finalize();
    44     44   		class = lib.str.plit'timeline';
    45     45   		cache = false;
    46     46   	}
    47     47   	co:livepage(doc,newest)
    48         -	doc.body:free()
           48  +	--doc.body:free()
    49     49   end
    50     50   return render_timeline

Modified render/tweet-page.t from [c0b864229b] to [304c6c3aff].

    26     26   render_tweet_page(
    27     27   	co: &lib.srv.convo,
    28     28   	path: lib.mem.ptr(pref),
    29     29   	p: &lib.store.post
    30     30   ): {}
    31     31   	var livetime = co.srv:thread_latest_arrival_calc(p.id)
    32     32   
    33         -	var pg: lib.str.acc pg:init(256)
           33  +	var pg = co:stra(256)
    34     34   	pg:lpush('<div data-live="10">') -- make the OP refresh too
    35     35   	lib.render.tweet(co, p, &pg)
    36     36   	pg:lpush('</div>')
    37     37   
    38     38   	if co.aid ~= 0 then
    39     39   		pg:lpush('<form class="action-bar" method="post">')
    40     40   		if not co.srv:post_liked_uid(co.who.id, p.id)
................................................................................
    58     58   	render_tweet_replies(co, &pg, p.id)
    59     59   	pg:lpush('</div>')
    60     60   
    61     61   	if co.aid ~= 0 and co.who.rights.powers.post() then
    62     62   		lib.render.compose(co, nil, &pg)
    63     63   	end
    64     64   
    65         -	var ppg = pg:finalize() defer ppg:free()
           65  +	var ppg = pg:finalize() --defer ppg:free()
    66     66   	co:livepage([lib.srv.convo.page] {
    67     67   		title = lib.str.plit 'post'; cache = false;
    68     68   		class = lib.str.plit 'post'; body = ppg;
    69     69   	}, livetime)
    70     70   
    71     71   	-- TODO display conversation
    72     72   	-- perhaps display descendant nodes here, and have a link to the top of the whole tree?
    73     73   end
    74     74   
    75     75   return render_tweet_page

Modified render/tweet.t from [83917dbbe9] to [57f4f6bb5e].

    34     34   		retweeter = co.actorcache:insert(co.srv:actor_fetch_uid(p.rtdby)).ptr
    35     35   	end
    36     36   
    37     37   	::foundauth::
    38     38   	var timestr: int8[26] lib.osclock.ctime_r(&p.posted, &timestr[0])
    39     39   	for i=0,26 do if timestr[i] == @'\n' then timestr[i] = 0 break end end -- 🙄
    40     40   
    41         -	var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
    42         -	defer bhtml:free()
           41  +	var bhtml = lib.smackdown.html(&co.srv.pool, [lib.mem.ptr(int8)] {ptr=p.body,ct=0},false)
           42  +	--defer bhtml:free()
    43     43   
    44     44   	var idbuf: int8[lib.math.shorthand.maxlen]
    45     45   	var idlen = lib.math.shorthand.gen(p.id, idbuf)
    46         -	var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
           46  +	var permalink: lib.str.acc permalink:pool(&co.srv.pool, 7+idlen):lpush('/post/'):push(idbuf,idlen)
    47     47   	var fullname = lib.render.nym(author,0,nil, false) defer fullname:free()
    48     48   	var tpl = data.view.tweet {
    49     49   		text = bhtml;
    50     50   		subject = cs(lib.coalesce(p.subject,''));
    51     51   		nym = fullname;
    52     52   		when = cs(&timestr[0]);
    53     53   		avatar = cs(author.avatar);
................................................................................
    61     61   		var parent = co.srv:post_fetch(p.parent) defer parent:free()
    62     62   		if not parent then
    63     63   			lib.bail('schema integrity violation - could not match post to parent')
    64     64   		end
    65     65   		var pauth = co.srv:actor_fetch_uid(parent(0).author) defer pauth:free()
    66     66   		var pidbuf: int8[lib.math.shorthand.maxlen]
    67     67   		var pidlen = lib.math.shorthand.gen(p.parent, pidbuf)
    68         -		var pa: lib.str.acc pa:init(128)
           68  +		var pa: lib.str.acc pa:pool(&co.srv.pool, 128)
    69     69   		pa:lpush('<small>in reply to <a class="username" href="/post/'):push(&pidbuf[0],pidlen):lpush('">')
    70     70   		lib.render.nym(pauth.ptr,0,&pa,true)
    71     71   		pa:lpush('</a></small>')
    72     72   		tpl.extra = pa:finalize()
    73     73   	end
    74     74   	if p.rts + p.likes > 0 then
    75         -		var s: lib.str.acc s:init(128)
           75  +		var s: lib.str.acc s:pool(&co.srv.pool,128)
    76     76   		s:lpush('<div class="stats">')
    77     77   		if p.rts   > 0 then s:lpush('<div class="rt">'  ):dpush(p.rts  ):lpush('</div>') end
    78     78   		if p.likes > 0 then s:lpush('<div class="like">'):dpush(p.likes):lpush('</div>') end
    79     79   		s:lpush('</div>')
    80     80   		tpl.stats = s:finalize()
    81     81   	end
    82     82   
................................................................................
    91     91   		attrcur = lib.str.cpy(attrcur, '"')
    92     92   	end
    93     93   	if co.aid ~= 0 and p.author == co.who.id then attrcur = lib.str.cpy(attrcur, ' data-own') end
    94     94   	if retweeter ~= nil then attrcur = lib.str.cpy(attrcur, ' data-rt') end
    95     95   
    96     96   	if attrcur ~= &attrbuf[0] then tpl.attr = &attrbuf[0] end
    97     97   
    98         -	defer tpl.permalink:free()
           98  +	--defer tpl.permalink:free()
    99     99   	if acc ~= nil then
   100    100   		if retweeter ~= nil then push_promo_header(co, acc, retweeter, p.rtact) end
   101    101   		tpl:append(acc)
   102    102   		if retweeter ~= nil then acc:lpush('</div>') end
   103         -		if p.rts + p.likes > 0 then tpl.stats:free() end
   104         -		if tpl.extra.ct > 0 then tpl.extra:free() end
          103  +		--if p.rts + p.likes > 0 then tpl.stats:free() end
          104  +		--if tpl.extra.ct > 0 then tpl.extra:free() end
   105    105   		return [lib.mem.ptr(int8)]{ptr=nil,ct=0}
   106    106   	end
   107    107   
   108    108   	if retweeter ~= nil then
   109         -		var rta: lib.str.acc rta:init(512)
          109  +		var rta: lib.str.acc rta:pool(&co.srv.pool,512)
   110    110   		push_promo_header(co, &rta, retweeter, p.rtact)
   111    111   		tpl:append(&rta) rta:lpush('</div>')
   112         -		if tpl.extra.ct > 0 then tpl.extra:free() end
          112  +		--if tpl.extra.ct > 0 then tpl.extra:free() end
   113    113   		return rta:finalize()
   114    114   	else
   115         -		var txt = tpl:tostr()
   116         -		if tpl.extra.ct > 0 then tpl.extra:free() end
   117         -		if p.rts + p.likes > 0 then tpl.stats:free() end
          115  +		var txt = tpl:poolstr(&co.srv.pool)
          116  +		--if tpl.extra.ct > 0 then tpl.extra:free() end
          117  +		--if p.rts + p.likes > 0 then tpl.stats:free() end
   118    118   		return txt
   119    119   	end
   120    120   end
   121    121   return render_tweet

Modified render/user-page.t from [e4691ec838] to [be37fdb666].

     9      9   	if co.aid ~= 0 and co.who.id == actor.id then
    10     10   		ti:compose('my profile')
    11     11   	else
    12     12   		ti:compose('profile :: ', actor.handle)
    13     13   	end
    14     14   	var tiptr = ti:finalize()
    15     15   
    16         -	var acc: lib.str.acc acc:init(1024)
    17         -	var pftxt = lib.render.profile(co,actor,relationship) defer pftxt:free()
           16  +	var acc: lib.str.acc acc:pool(&co.srv.pool, 1024)
           17  +	var pftxt = lib.render.profile(co,actor,relationship) --defer pftxt:free()
    18     18   	acc:ppush(pftxt)
    19     19   
    20     20   	var stoptime = lib.osclock.time(nil)
    21     21   	var posts = co.srv:post_enum_author_uid(actor.id, lib.store.range {
    22     22   		mode = 1; -- T->I
    23     23   		from_time = stoptime;
    24     24   		to_idx = 64;
................................................................................
    39     39   	co:livepage([lib.srv.convo.page] {
    40     40   		title = tiptr; body = bdf;
    41     41   		class = lib.str.plit 'profile';
    42     42   		cache = false;
    43     43   	}, newest)
    44     44   
    45     45   	tiptr:free()
    46         -	bdf:free()
           46  +	--bdf:free()
    47     47   end
    48     48   
    49     49   return render_userpage

Modified route.t from [2b1fe64d41] to [a232000d86].

   248    248   				else
   249    249   					conf = data.view.confirm {
   250    250   						title = lib.str.plit 'cancel retweet';
   251    251   						query = lib.str.plit 'are you sure you want to undo this retweet?';
   252    252   						cancel = lib.str.plit'/';
   253    253   					}
   254    254   				end
   255         -				var body = conf:tostr() defer body:free()
          255  +				var fr = co.srv.pool:frame()
          256  +				var body = conf:poolstr(&co.srv.pool) --defer body:free()
   256    257   				co:stdpage([lib.srv.convo.page] {
   257    258   					title = lib.str.plit 'post :: delete';
   258    259   					class = lib.str.plit 'query';
   259    260   					body = body; cache = false;
   260    261   				})
          262  +				co.srv.pool:reset(fr)
   261    263   				return
   262    264   			elseif meth == method.post then
   263    265   				var act = co:ppostv('act')
   264    266   				if act:cmp(lib.str.plit 'confirm') then
   265    267   					if post:ref() then
   266    268   						post(0).source:post_destroy(post(0).id)
   267    269   					elseif rt.kind ~= 0 then
................................................................................
   513    515   
   514    516   terra http.media_manager(co: &lib.srv.convo, path: hpath, meth: method.t, uid: uint64)
   515    517   	if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
   516    518   		if meth == method.get then
   517    519   			var view = data.view.media_upload {
   518    520   				folders = ''
   519    521   			}
   520         -			var pg = view:tostr() defer pg:free()
          522  +			var pg = view:poolstr(&co.srv.pool) -- defer pg:free()
   521    523   			co:stdpage([lib.srv.convo.page] {
   522    524   				title = lib.str.plit'media :: upload';
   523    525   				class = lib.str.plit'media upload';
   524    526   				cache = false; body = pg;
   525    527   			})
   526    528   		elseif meth == method.post_file then
   527    529   			var desc = pstring.null()

Modified smackdown.t from [e99ea3e622] to [ad4d039b1c].

    52     52   local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr)
    53     53   	var sl = scanline(l,max,n,nc)
    54     54   	if sl == nil then return nil else sl = sl + nc end
    55     55   	if sl >= l+max or not isws(@(sl-1)) then return sl-nc end
    56     56   	return nil
    57     57   end
    58     58   
    59         -terra m.html(input: pstr, firstline: bool)
           59  +terra m.html(pool: &lib.mem.pool, input: pstr, firstline: bool)
    60     60   	if input.ptr == nil then return pstr.null() end
    61     61   	if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end
    62     62   
    63         -	var md = lib.html.sanitize(input,false)
           63  +	var md = lib.html.sanitize(pool,input,false)
    64     64   
    65         -	var styled: lib.str.acc styled:init(md.ct)
           65  +	var styled: lib.str.acc styled:pool(pool,md.ct)
    66     66   
    67     67   	do var i = 0 while i < md.ct do
    68     68   		--var wordstart = (i == 0 or isws(md.ptr[i-1]))
    69     69   		--var wordend = (i == md.ct - 1 or isws(md.ptr[i+1]))
    70     70   		var wordstart = (i + 1 < md.ct and not isws(md.ptr[i+1]))
    71     71   		var wordend = (i == md.ct - 1 or not isws(md.ptr[i-1]))
    72     72   
................................................................................
   118    118   				goto skip
   119    119   			end
   120    120   		end
   121    121   
   122    122   		::fallback::styled:push(here,1) -- :/
   123    123   		i = i + 1
   124    124   	::skip::end end
   125         -	md:free()
          125  +	--md:free()
   126    126   
   127    127   	-- we make two passes: the first detects and transforms inline elements,
   128    128   	-- the second carries out block-level organization
   129    129   
   130         -	var html: lib.str.acc html:init(styled.sz)
          130  +	var html: lib.str.acc html:pool(pool,styled.sz)
   131    131   	var s = state {
   132    132   		segt = segt.none;
   133    133   		bqlvl = 0;
   134    134   		curpos = md.ptr;
   135    135   		blockstart = nil;
   136    136   	}
   137    137   	while s.curpos < md.ptr + md.ct do
   138    138   		s.curpos = s.curpos + 1
   139    139   	end 
   140    140   
   141         -		html:free() -- JUST FOR NOW
          141  +		--html:free() -- JUST FOR NOW
   142    142   	return styled:finalize()
   143    143   end
   144    144   
   145    145   return m

Modified srv.t from [80adbc5ad3] to [854410a8ca].

     5      5   local struct srv
     6      6   local struct cfgcache {
     7      7   	secret: pstring
     8      8   	pol_sec: secmode.t
     9      9   	pol_reg: bool
    10     10   	credmgd: bool
    11     11   	maxupsz: intptr
           12  +	poolinitsz: intptr
    12     13   	instance: pstring
    13     14   	overlord: &srv
    14     15   	ui_cue_staff: pstring
    15     16   	ui_cue_founder: pstring
    16     17   	ui_hue: uint16
    17     18   	nranks: uint16
    18     19   	maxinvites: uint16
................................................................................
    20     21   }
    21     22   local struct srv {
    22     23   	sources: lib.mem.ptr(lib.store.source)
    23     24   	webmgr: lib.net.mg_mgr
    24     25   	webcon: &lib.net.mg_connection
    25     26   	cfg: cfgcache
    26     27   	id: rawstring
           28  +	pool: lib.mem.pool
    27     29   }
    28     30   
    29     31   terra cfgcache:free() -- :/
    30     32   	self.secret:free()
    31     33   	self.instance:free()
    32     34   	self.ui_cue_staff:free()
    33     35   	self.ui_cue_founder:free()
................................................................................
   306    308   
   307    309   terra convo:confirm(title: pstring, msg: pstring, cancel: pstring)
   308    310   	var conf = data.view.confirm {
   309    311   		title = title;
   310    312   		query = msg;
   311    313   		cancel = cancel;
   312    314   	}
   313         -	var ti: lib.str.acc ti:compose('confirm :: ', title)
   314         -	var body = conf:tostr() defer body:free()
          315  +	var ti: lib.str.acc ti:pcompose(&self.srv.pool,'confirm :: ', title)
          316  +	var body = conf:poolstr(&self.srv.pool) -- defer body:free()
   315    317   	var cf = [convo.page] {
   316    318   		title = ti:finalize();
   317    319   		class = lib.str.plit 'query';
   318    320   		body = body; cache = false;
   319    321   	}
   320    322   	self:stdpage(cf)
   321         -	cf.title:free()
          323  +	--cf.title:free()
          324  +end
          325  +
          326  +terra convo:stra(sz: intptr) -- convenience function
          327  +	var s: lib.str.acc
          328  +	s:pool(&self.srv.pool,sz)
          329  +	return s
   322    330   end
   323    331   
   324    332   convo.methods.assertpow = macro(function(self, pow)
   325    333   	return quote
   326    334   		var ok = true
   327    335   		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
   328    336   			ok = false
................................................................................
   330    338   		end
   331    339   	in ok end
   332    340   end)
   333    341   
   334    342   -- CALL ONLY ONCE PER VAR
   335    343   terra convo:postv(name: rawstring)
   336    344   	if self.varbuf.ptr == nil then
   337         -		self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
          345  +		self.varbuf = self.srv.pool:alloc(int8, self.msg.body.len + self.msg.query.len)
   338    346   		self.vbofs = self.varbuf.ptr
   339    347   	end
   340    348   	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
   341    349   	if o > 0 then
   342    350   		var r = self.vbofs
   343    351   		self.vbofs = self.vbofs + o + 1
   344    352   		@(self.vbofs - 1) = 0
................................................................................
   349    357   terra convo:ppostv(name: rawstring)
   350    358   	var s,l = self:postv(name)
   351    359   	return pstring { ptr = s, ct = l }
   352    360   end
   353    361   
   354    362   terra convo:getv(name: rawstring)
   355    363   	if self.varbuf.ptr == nil then
   356         -		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
          364  +		self.varbuf = self.srv.pool:alloc(int8, self.msg.query.len + self.msg.body.len)
   357    365   		self.vbofs = self.varbuf.ptr
   358    366   	end
   359    367   	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
   360    368   	if o > 0 then
   361    369   		var r = self.vbofs
   362    370   		self.vbofs = self.vbofs + o + 1
   363    371   		@(self.vbofs - 1) = 0
................................................................................
   551    559   				var livelast_p = lib.http.findheader(msg, 'X-Live-Last-Arrival')
   552    560   				if livelast_p ~= nil and livelast_p.ptr ~= nil then
   553    561   					var ll, ok = lib.math.decparse(pstring{ptr = livelast_p.ptr, ct = livelast_p.ct - 1})
   554    562   					if ok then co.live_last = ll end
   555    563   				end
   556    564   
   557    565   
   558         -				var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free()
          566  +				var uridec = server.pool:alloc(int8, msg.uri.len)
   559    567   				var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1)
   560    568   
   561    569   				var uri = uridec
   562    570   				if urideclen == -1 then
   563    571   					for i = 0,msg.uri.len do
   564    572   						if msg.uri.ptr[i] == @'+'
   565    573   							then uri.ptr[i] = @' '
................................................................................
   677    685   							bsr:free()
   678    686   							upmap:free()
   679    687   						end
   680    688   					end
   681    689   				end
   682    690   
   683    691   				route.dispatch_http(&co, uri, co.method)
          692  +
          693  +				::fail::
   684    694   				if co.uploads.run > 0 then
   685    695   					for i=0,co.uploads.sz do
   686    696   						co.uploads(i).filename:free()
   687    697   						co.uploads(i).field:free()
   688    698   					end
   689    699   					co.uploads:free()
   690    700   				end
   691    701   
   692         -				::fail::
   693    702   				if co.aid ~= 0 then lib.mem.heapf(co.who) end
   694         -				if co.varbuf.ptr ~= nil then co.varbuf:free() end
   695         -				if co.navbar.ptr ~= nil then co.navbar:free() end
          703  +				-- if co.varbuf.ptr ~= nil then co.varbuf:free() end
          704  +				-- if co.navbar.ptr ~= nil then co.navbar:free() end
   696    705   				co.actorcache:free()
          706  +				server.pool:clear()
   697    707   			end
   698    708   		end
   699    709   	end;
   700    710   }
   701    711   
   702    712   local terra cfg(s: &srv, befile: rawstring)
   703    713   	lib.report('configuring backends from ', befile)
................................................................................
   853    863   		lib.bail('could not connect to any data sources!')
   854    864   	end
   855    865   end
   856    866   
   857    867   terra srv:start(iname: rawstring)
   858    868   	self:conprep(lib.store.prepmode.full)
   859    869   	self.cfg:init(self)
          870  +	self.pool:init(self.cfg.poolinitsz)
   860    871   	var dbbind = self:conf_get('bind')
   861    872   	if iname == nil then iname = lib.proc.getenv('parsav_instance') end
   862    873   	if iname == nil then
   863    874   		self.id = self.cfg.instance.ptr;
   864    875   		-- let this leak -- it'll be needed for the lifetime of the process anyway
   865    876   	else self.id = iname end 
   866    877   
................................................................................
   890    901   terra srv:shutdown()
   891    902   	lib.net.mg_mgr_free(&self.webmgr)
   892    903   	for i=0,self.sources.ct do var src = self.sources.ptr + i
   893    904   		lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
   894    905   		src:close()
   895    906   	end
   896    907   	self.sources:free()
          908  +	self.pool:free()
   897    909   end
   898    910   
   899    911   terra cfgcache:cfint(name: rawstring, default: intptr)
   900    912   	var str = self.overlord:conf_get(name)
   901    913   	if str.ptr ~= nil then
   902    914   		var i,ok = lib.math.decparse(str)
   903    915   		if ok then default = i else
   904    916   			lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected integer; using default value instead')
   905    917   		end
   906    918   		str:free()
          919  +	end
          920  +	return default
          921  +end
          922  +
          923  +terra cfgcache:cffsz(name: rawstring, default: intptr)
          924  +	var str = self.overlord:conf_get(name)
          925  +	if str:ref() then
          926  +		var sz, ok = lib.math.fsz_parse(str)
          927  +		if ok then default = sz else
          928  +			lib.warn('invalid configuration setting ',name,'="',{str.ptr,str.ct},'", expected byte length; using default value instead')
          929  +		end
          930  +		str:free()
   907    931   	end
   908    932   	return default
   909    933   end
   910    934   
   911    935   terra cfgcache:cfbool(name: rawstring, default: bool)
   912    936   	var str = self.overlord:conf_get(name)
   913    937   	if str.ptr ~= nil then
................................................................................
   937    961   		if lib.str.cmp(sreg.ptr, 'managed') == 0
   938    962   			then self.credmgd = true
   939    963   			else self.credmgd = false
   940    964   		end
   941    965   		sreg:free()
   942    966   	end end
   943    967   
   944         -	do self.maxupsz = [1024 * 100] -- 100 kilobyte default
   945         -	var sreg = self.overlord:conf_get('maximum-artifact-size')
   946         -	if sreg:ref() then
   947         -		var sz, ok = lib.math.fsz_parse(sreg)
   948         -		if ok then self.maxupsz = sz else
   949         -			lib.warn('invalid configuration value for maximum-artifact-size; keeping default 100K upload limit')
   950         -		end
   951         -		sreg:free() end
   952         -	end
          968  +	self.maxupsz = self:cffsz('maximum-artifact-size', [1024 * 100]) -- 100 kilobyte default
          969  +	self.poolinitsz = self:cffsz('server-pool-size-initial', [1024 * 10]) -- 10 kilobyte default
   953    970   	
   954    971   	self.pol_sec = secmode.lockdown
   955    972   	var smode = self.overlord:conf_get('policy-security')
   956    973   	if smode.ptr ~= nil then
   957    974   		if lib.str.cmp(smode.ptr, 'public') == 0 then
   958    975   			self.pol_sec = secmode.public
   959    976   		elseif lib.str.cmp(smode.ptr, 'private') == 0 then
................................................................................
   970    987   	self.nranks = self:cfint('user-ranks',10)
   971    988   	self.maxinvites = self:cfint('max-invites',64)
   972    989   	
   973    990   	var webmaster = self.overlord:conf_get('master')
   974    991   	if webmaster:ref() then defer webmaster:free()
   975    992   		var wma = self.overlord:actor_fetch_xid(webmaster)
   976    993   		if not wma then
   977         -			lib.warn('the webmaster specified in the configuration store does not seem to exist or is not known to this instance; preceding as if no master defined. if the master is a remote user, you can rectify this with the `actor ',{webmaster.ptr,webmaster.ct},' instantiate` and `conf refresh` commands')
          994  +			lib.warn('the webmaster specified in the configuration store does not seem to exist or is not known to this instance; preceding as if no master defined. if the master is a remote user, you can rectify this with the `actor "',{webmaster.ptr,webmaster.ct},'" instantiate` and `conf refresh` commands')
   978    995   		else
   979    996   			self.master = wma(0).id
   980    997   			wma:free()
   981    998   		end
   982    999   	end
   983   1000   
   984   1001   	self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff')

Modified str.t from [d98a573fe7] to [89921fa130].

   107    107   end
   108    108   
   109    109   struct m.acc {
   110    110   	buf: rawstring
   111    111   	sz: intptr
   112    112   	run: intptr
   113    113   	space: intptr
          114  +	pool: &lib.mem.pool
   114    115   }
   115    116   
   116    117   terra m.cdowncase(c: int8)
   117    118   	if c >= @'A' and c <= @'Z' then
   118    119   		return c + (@'a' - @'A')
   119    120   	else return c end
   120    121   end
................................................................................
   127    128   
   128    129   local terra biggest(a: intptr, b: intptr)
   129    130   	if a > b then return a else return b end
   130    131   end
   131    132   
   132    133   terra m.acc:init(run: intptr)
   133    134   	--lib.dbg('initializing string accumulator')
          135  +	self.pool = nil
   134    136   	if run == 0 then
   135    137   		lib.warn('attempted to allocate zero-length string accumulator')
   136    138   		self.buf = nil
   137    139   	else
   138    140   		self.buf = [rawstring](lib.mem.heapa_raw(run))
   139    141   		if self.buf == nil then
   140    142   			lib.warn('string buffer allocation failed, very little memory availble')
................................................................................
   141    143   		end
   142    144   	end
   143    145   	self.run = lib.trn(self.buf == nil, 0, run)
   144    146   	self.space = self.run
   145    147   	self.sz = 0
   146    148   	return self
   147    149   end;
          150  +
          151  +terra m.acc:pool(pool: &lib.mem.pool, run: intptr)
          152  +	self.buf = [&int8](pool:alloc_bytes(run))
          153  +	self.pool = pool
          154  +	self.run = run
          155  +	self.space = self.run
          156  +	self.sz = 0
          157  +	return self
          158  +end
   148    159   
   149    160   terra m.acc:free()
   150    161   	--lib.dbg('freeing string accumulator')
          162  +	if self.pool ~= nil then
          163  +		lib.dbg('attempted to free pooled string accumulator; use frame-reset instead')
          164  +		return
          165  +	end
   151    166   	if self.buf ~= nil and self.space > 0 then
   152    167   		lib.mem.heapf(self.buf)
   153    168   	end
   154    169   end;
   155    170   
   156    171   terra m.acc:crush()
   157    172   	--lib.dbg('crushing string accumulator')
          173  +	if self.pool ~= nil then return self end -- no point unless at end of buffer
   158    174   	self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.sz))
   159    175   	self.space = self.sz
   160    176   	return self
   161    177   end;
   162    178   
   163    179   terra m.acc:finalize()
   164    180   	--lib.dbg('finalizing string accumulator')
................................................................................
   169    185   	self.buf = nil
   170    186   	self.sz = 0
   171    187   	return pt
   172    188   end;
   173    189   
   174    190   terra m.acc:cue(sz: intptr)
   175    191   	if sz <= self.run then return end
          192  +	var curspace = self.space
   176    193   	self.run = sz
   177    194   	if self.space - self.sz < self.run then
   178    195   		self.space = self.sz + self.run
   179         -		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
          196  +		if self.pool ~= nil then
          197  +			self.buf = [&int8](self.pool:realloc_bytes(self.buf, curspace, self.space))
          198  +		else
          199  +			self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
          200  +		end
   180    201   	end
   181    202   end
   182    203   
   183    204   terra m.acc:reset() -- semantic convenience function
   184    205   	self.sz = 0
   185    206   end
   186    207   
................................................................................
   189    210   	if str == nil then return self end
   190    211   	--if str[len - 1] == 0xA then llen = llen - 1 end -- don't display newlines in debug output
   191    212   	-- lib.dbg('pushing "',{str,llen},'" onto accumulator')
   192    213   	if self.buf == nil then self:init(self.run) end
   193    214   	if self.buf == nil then lib.warn('attempted to push string onto unallocated accumulator') return self end
   194    215   	if len == 0 then len = m.sz(str) end
   195    216   	if len >= self.space - self.sz then
   196         -		self.space = self.space + biggest(self.run,len + 1)
   197         -		self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
          217  +		self:cue(self.space + biggest(self.run,len + 1))
          218  +		--self.space = self.space + biggest(self.run,len + 1)
          219  +		--self.buf = [rawstring](lib.mem.heapr_raw(self.buf, self.space))
   198    220   	end
   199    221   	lib.mem.cpy(self.buf + self.sz, str, len)
   200    222   	self.sz = self.sz + len
   201    223   	self.buf[self.sz] = 0
   202    224   	return self
   203    225   end;
   204    226   
................................................................................
   242    264   	return `self:push([str:asvalue()], [#(str:asvalue())]) end)
   243    265   m.acc.methods.ppush = terra(self: &m.acc, str: lib.mem.ptr(int8))
   244    266   	self:push(str.ptr, str.ct)            return self end;
   245    267   m.acc.methods.rpush = terra(self: &m.acc, str: lib.mem.ref(int8))
   246    268   	self:push(str.ptr, str.ct)            return self end;
   247    269   m.acc.methods.merge = terra(self: &m.acc, str: lib.mem.ptr(int8))
   248    270   	self:push(str.ptr, str.ct) str:free() return self end;
   249         -m.acc.methods.compose = macro(function(self, ...)
          271  +local composefn = function(call, ...)
   250    272   	local minlen = 0
   251    273   	local pstrs = {}
   252    274   	for i,v in ipairs{...} do
   253    275   		if type(v) == 'table' then
   254    276   			local gl = 16 -- guess wildly
   255    277   			if v.tree and v.tree.type.convertible == 'tuple' then
   256    278   				pstrs[#pstrs+1] = {str = `v._0, len = `v._1}
................................................................................
   263    285   			else pstrs[#pstrs+1] = {str = v, len = 0} end
   264    286   			minlen = minlen + gl
   265    287   		elseif type(v) == 'string' then 
   266    288   			pstrs[#pstrs+1] = {str = v, len = #v}
   267    289   			minlen = minlen + #v + 1
   268    290   		else error('invalid type in compose expression') end
   269    291   	end
   270         -	local call = `self:init(minlen)
          292  +	call = call(minlen) --`self:init(minlen)
   271    293   	for i,v in ipairs(pstrs) do
   272    294   		call = `[call]:push([v.str],[v.len])
   273    295   	end
   274    296   	return call
          297  +end
          298  +m.acc.methods.compose = macro(function(self, ...)
          299  +	return composefn(function(minlen) return `self:init(minlen) end, ...)
          300  +end)
          301  +m.acc.methods.pcompose = macro(function(self, pool, ...)
          302  +	return composefn(function(minlen) return `self:pool(pool,minlen) end, ...)
   275    303   end)
          304  +
   276    305   m.acc.metamethods.__lshift = terralib.overloadedfunction('(<<)', {
   277    306   	terra(self: &m.acc, str: rawstring)         return self: push(str,0) end;
   278    307   	terra(self: &m.acc, str: lib.mem.ptr(int8)) return self:ppush(str  ) end;
   279    308   })
   280    309   
   281    310   m.box = terralib.memoize(function(ty)
   282    311   	local b = struct {

Modified tpl.t from [9f68e11c52] to [db48a650e7].

    89     89   	end
    90     90   
    91     91   	local copiers = {}
    92     92   	local senders = {}
    93     93   	local appenders = {}
    94     94   	local symtxt = symbol(lib.mem.ptr(int8))
    95     95   	local cpypos = symbol(&opaque)
           96  +	local pool = symbol(&lib.mem.pool)
    96     97   	local accumulator = symbol(&lib.str.acc)
    97     98   	local destcon = symbol(&lib.net.mg_connection)
    98     99   	for idx, seg in ipairs(segs) do
    99    100   		copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
   100    101   		senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
   101    102   		appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
   102    103   		if fields[idx] and fields[idx].mode then
   103    104   			local f = fields[idx]
   104    105   			local fp = `symself.[f.key]
   105    106   			copiers[#copiers+1] = quote 
   106    107   				if fp.ct > 0 then
   107         -					var san = lib.html.sanitize(fp, [f.mode == ':'])
          108  +					var san = lib.html.sanitize(pool, fp, [f.mode == ':'])
   108    109   					[cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct)
   109    110   					--san:free()
   110    111   				end
   111    112   			end
   112    113   			senders[#senders+1] = quote
   113    114   				if fp.ct > 0 then
   114         -					var san = lib.html.sanitize(fp, [f.mode == ':'])
          115  +					var san = lib.html.sanitize(pool, fp, [f.mode == ':'])
   115    116   					lib.net.mg_send([destcon], san.ptr, san.ct)
   116    117   					--san:free()
   117    118   				end
   118    119   			end
   119    120   			appenders[#appenders+1] = quote
   120    121   				if fp.ct > 0 then
   121         -					var san = lib.html.sanitize(fp, [f.mode == ':'])
          122  +					var san = lib.html.sanitize(pool, fp, [f.mode == ':'])
   122    123   					[accumulator]:ppush(san)
   123    124   					--san:free()
   124    125   				end
   125    126   			end
   126    127   		elseif fields[idx] then
   127    128   			local f = fields[idx]
   128    129   			local fp = `symself.[f.key]
................................................................................
   139    140   			appenders[#appenders+1] = quote
   140    141   				if fp.ct > 0 then [accumulator]:ppush(fp) end
   141    142   			end
   142    143   		end
   143    144   	end
   144    145   
   145    146   	local tid = tplspec.id or '<anonymous>'
          147  +	rec.methods.poolstr = terra([symself],[pool])
          148  +		lib.dbg(['pooling template ' .. tid])
          149  +		[tallyup]
          150  +		var [symtxt] = pool:alloc(int8, [runningtally])
          151  +		var [cpypos] = [&opaque](symtxt.ptr)
          152  +		[copiers]
          153  +		@[&int8](cpypos) = 0
          154  +		symtxt.ct = [&int8](cpypos) - symtxt.ptr
          155  +		return symtxt
          156  +	end
   146    157   	rec.methods.tostr = terra([symself])
   147    158   		lib.dbg(['compiling template ' .. tid])
   148    159   		[tallyup]
   149    160   		var [symtxt] = lib.mem.heapa(int8, [runningtally])
   150    161   		var [cpypos] = [&opaque](symtxt.ptr)
          162  +		var p: lib.mem.pool p:init(128)
          163  +		var [pool] = &p
   151    164   		[copiers]
   152    165   		@[&int8](cpypos) = 0
   153    166   		symtxt.ct = [&int8](cpypos) - symtxt.ptr
          167  +		pool:free()
   154    168   		return symtxt
   155    169   	end
   156    170   	rec.methods.append = terra([symself], [accumulator])
          171  +		var [pool]
          172  +		var p: lib.mem.pool
          173  +		if [accumulator].pool == nil then
          174  +			p:init(128)
          175  +			pool = &p
          176  +		else pool = [accumulator].pool end
   157    177   		lib.dbg(['appending template ' .. tid])
   158    178   		[tallyup]
   159    179   		accumulator:cue([runningtally])
   160    180   		[appenders]
          181  +		if [accumulator].pool == nil then p:free() end
   161    182   		return accumulator
   162    183   	end
   163    184   	rec.methods.head = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
          185  +		var p: lib.mem.pool p:init(128) -- FIXME
          186  +		var [pool] = &p
   164    187   		lib.dbg(['transmitting template headers ' .. tid])
   165    188   		[tallyup]
   166    189   		lib.net.mg_printf([destcon], 'HTTP/1.1 %s', lib.http.codestr(code))
   167    190   		for i = 0, hd.ct do
   168    191   			lib.net.mg_printf([destcon], '%s: %s\r\n', hd.ptr[i].key, hd.ptr[i].value)
   169    192   		end
   170    193   		lib.net.mg_printf([destcon],'Content-Length: %llu\r\n\r\n', [runningtally] + 1)
          194  +		p:free()
   171    195   	end
   172    196   	rec.methods.send = terra([symself], [destcon], code: uint16, hd: lib.mem.ptr(lib.http.header))
          197  +		var p: lib.mem.pool p:init(128) -- FIXME
          198  +		var [pool] = &p
   173    199   		lib.dbg(['transmitting template ' .. tid])
   174    200   
   175    201   		symself:head(destcon,code,hd)
   176    202   
   177    203   		[senders]
   178    204   		lib.net.mg_send([destcon],'\r\n',2)
          205  +		p:free()
   179    206   	end
   180    207   	rec.methods.sz = terra([symself])
   181    208   		lib.dbg(['tallying template ' .. tid])
   182    209   		[tallyup]
   183    210   		return [runningtally] + 1
   184    211   	end
   185    212   
   186    213   	return rec
   187    214   end
   188    215   
   189    216   return m