1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00

Implement hash tables for virtual subshells (re: 102868f8, 9d428f8f)

The forking fix implemented in 102868f8 and 9d428f8f, which stops
the main shell's hash table from being cleared if PATH is changed
in a subshell, can cause a significant performance penalty for
certain scripts that do something like

    ( PATH=... command foo )

in a subshell, especially if done repeatedly. This is because the
hash table is cleared (and hence a subshell forks) even for
temporary PATH assignments preceding commands.

It also just plain doesn't work. For instance:

    $ hash -r; (ls) >/dev/null; hash
    ls=/bin/ls

Simply running an external command in a subshell caches the path in
the hash table that is shared with a main shell. To remedy this, we
would have to fork the subshell before forking any external
command. And that would be an unacceptable performance regression.

Virtual subshells do not need to fork when changing PATH if they
get their own hash tables. This commit adds these. The code for
alias subshell trees (which was removed in ec888867 because they
were broken and unneeded) provided the beginning of a template for
their implementation.

src/cmd/ksh93/sh/subshell.c:
- struct subshell: Add strack pointer to subshell hash table.
- Add sh_subtracktree(): return pointer to subshell hash table.
- sh_subfuntree(): Refactor a bit for legibility.
- sh_subshell(): Add code for cleaning up subshell hash table.

src/cmd/ksh93/sh/name.c:
- nv_putval(): Remove code to fork a subshell upon resetting PATH.
- nv_rehash(): When in a subshell, invalidate a hash table entry
  for a subshell by creating the subshell scope if needed, then
  giving that entry the NV_NOALIAS attribute to invalidate it.

src/cmd/ksh93/sh/path.c: path_search():
- To set a tracked alias/hash table entry, use sh_subtracktree()
  and pass the HASH_NOSCOPE flag to nv_search() so that any new
  entries are added to the current subshell table (if any) and do
  not influence any parent scopes.

src/cmd/ksh93/bltins/typeset.c: b_alias():
- b_alias(): For hash table entries, use sh_subtracktree() instead
  of forking a subshell. Keep forking for normal aliases.
- setall(): To set a tracked alias/hash table entry, pass the
  HASH_NOSCOPE flag to nv_search() so that any new entries are
  added to the current subshell table (if any) and do not influence
  any parent scopes.

src/cmd/ksh93/sh/init.c: put_restricted():
- Update code for clearing the hash table (when changing $PATH) to
  use sh_subtracktree().

src/cmd/ksh93/bltins/cd_pwd.c:
- When invalidating path name bindings to relative paths, use the
  subshell hash tree if applicable by calling sh_subtracktree().
- rehash(): Call nv_rehash() instead of _nv_unset()ting the hash
  table entry; this is needed to work correctly in subshells.

src/cmd/ksh93/tests/leaks.sh:
- Add leak tests for various PATH-related operations in the main
  shell and in a virtual subshell.
- Several pre-existing memory leaks are exposed by the new tests
  (I've confirmed these in 93u+). The tests are disabled and marked
  TODO for now, as these bugs have not yet been fixed.

src/cmd/ksh93/tests/subshell.sh:
- Update.

Resolves: https://github.com/ksh93/ksh/issues/66
This commit is contained in:
Martijn Dekker 2021-01-07 21:18:33 +00:00
parent a95d107ee5
commit 222515bf08
11 changed files with 177 additions and 67 deletions

View file

@ -317,7 +317,7 @@ static void put_restricted(register Namval_t* np,const char *val,int flags,Namfu
if(np==PATHNOD || (path_scoped=(strcmp(name,PATHNOD->nvname)==0)))
{
/* Clear the hash table */
nv_scan(shp->track_tree,nv_rehash,(void*)0,NV_TAGGED,NV_TAGGED);
nv_scan(sh_subtracktree(1),nv_rehash,(void*)0,NV_TAGGED,NV_TAGGED);
if(path_scoped && !val)
val = PATHNOD->nvalue.cp;
}

View file

@ -1570,17 +1570,6 @@ void nv_putval(register Namval_t *np, const char *string, int flags)
#endif /* SHOPT_FIXEDARRAY */
if(!(flags&NV_RDONLY) && nv_isattr (np, NV_RDONLY))
errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np));
/*
* Resetting the PATH in a non-forking subshell will reset the parent shell's
* hash table, so work around the problem by forking before sh_assignok
* -- however, don't do this if we were told to ignore the variable's readonly state (i.e.
* if the NV_RDONLY flag is set), because that means we're restoring the parent shell state
* after exiting a virtual subshell, and we would end up forking the parent shell instead.
*/
if(np == PATHNOD && !(flags & NV_RDONLY) && shp->subshell && !shp->subshare)
sh_subfork();
/*
* The following could cause the shell to fork if assignment
* would cause a side effect
@ -2244,6 +2233,11 @@ static int scanfilter(Dt_t *dict, void *arg, void *data)
void nv_rehash(register Namval_t *np,void *data)
{
NOT_USED(data);
if(sh.subshell)
{
/* invalidate subshell node; if none exists, add a dummy */
np = nv_search(nv_name(np),sh.track_tree,NV_ADD|HASH_NOSCOPE);
}
nv_onattr(np,NV_NOALIAS);
}

View file

@ -738,7 +738,7 @@ int path_search(Shell_t *shp,register const char *name,Pathcomp_t **oldpp, int f
}
else if(pp && !sh_isstate(SH_DEFPATH) && *name!='/' && flag<3)
{
if(np=nv_search(name,shp->track_tree,NV_ADD))
if(np=nv_search(name,sh_subtracktree(1),NV_ADD|HASH_NOSCOPE))
path_alias(np,pp);
}
return(0);

View file

@ -75,6 +75,7 @@ static struct subshell
Dt_t *var; /* variable table at time of subshell */
struct Link *svar; /* save shell variable table */
Dt_t *sfun; /* function scope for subshell */
Dt_t *strack;/* tracked alias scope for subshell */
Pathcomp_t *pathlist; /* for PATH variable */
struct Error_context_s *errcontext;
Shopt_t options;/* save shell options */
@ -393,6 +394,22 @@ static void nv_restore(struct subshell *sp)
sp->shpwd=save;
}
/*
* Return pointer to tracked alias tree (a.k.a. hash table, i.e. cached $PATH search results).
* Create new one if in a subshell and one doesn't exist and 'create' is non-zero.
*/
Dt_t *sh_subtracktree(int create)
{
register struct subshell *sp = subshell_data;
if(create && sh.subshell && !sh.subshare && sp && !sp->strack)
{
sp->strack = dtopen(&_Nvdisc,Dtset);
dtview(sp->strack,sh.track_tree);
sh.track_tree = sp->strack;
}
return(sh.track_tree);
}
/*
* return pointer to function tree
* create new one if in a subshell and one doesn't exist and create is non-zero
@ -400,15 +417,13 @@ static void nv_restore(struct subshell *sp)
Dt_t *sh_subfuntree(int create)
{
register struct subshell *sp = subshell_data;
if(!sp || sp->shp->curenv==0)
return(sh.fun_tree);
if(!sp->sfun && create && !sp->shp->subshare)
if(create && sh.subshell && !sh.subshare && sp && !sp->sfun)
{
sp->sfun = dtopen(&_Nvdisc,Dtoset);
dtview(sp->sfun,sp->shp->fun_tree);
sp->shp->fun_tree = sp->sfun;
dtview(sp->sfun,sh.fun_tree);
sh.fun_tree = sp->sfun;
}
return(sp->shp->fun_tree);
return(sh.fun_tree);
}
/*
@ -743,6 +758,29 @@ Sfio_t *sh_subshell(Shell_t *shp,Shnode_t *t, volatile int flags, int comsub)
{
int n;
shp->options = sp->options;
/* Clean up subshell hash table. */
if(sp->strack)
{
Namval_t *np, *prev_np;
/* Detach this scope from the unified view. */
shp->track_tree = dtview(sp->strack,0);
/* Delete (free) all elements of the subshell hash table. To allow dtnext() to
set the pointer to the next item, we have to delete one item beind the loop. */
prev_np = 0;
np = (Namval_t*)dtfirst(sp->strack);
while(np)
{
if(prev_np)
nv_delete(prev_np,sp->strack,0);
prev_np = np;
np = (Namval_t*)dtnext(sp->strack,np);
}
if(prev_np)
nv_delete(prev_np,sp->strack,0);
/* Close and free the table itself. */
dtclose(sp->strack);
}
/* Clean up subshell function table. */
if(sp->sfun)
{
shp->fun_tree = dtview(sp->sfun,0);