Child processes can modify ETS table?

Hello!

Is there any way to specify that an ets table can be modified only by the owning process and the children of the owning process? Setting protection to public works but allows any process to modify the table.

A usecase would be to spawn multiple children in the owning process (using task_async), each of will modify a unique set of rows in the ets table.

Thanks!

1 Like

Unfortunately no. You can do some tricks using give_away but its really more trouble than its worth.

1 Like

A :public table may or may not also be :named. An unnamed table can only be accessed using the table reference returned from :ets.new. Pass that reference to your child processes and only they will be able to work with the table.

Here’s some code that demonstrates this:

iex(1)> t = :ets.new(:secret_table, [:public])
#Reference<0.2500864750.288227330.231076>

iex(2)> spawn(fn -> IO.inspect :ets.lookup(:secret_table, :foo) end)
#PID<0.105.0>

10:13:17.716 [error] Process #PID<0.105.0> raised an exception
** (ArgumentError) argument error
    (stdlib) :ets.lookup(:secret_table, :foo)
    (stdlib) erl_eval.erl:680: :erl_eval.do_apply/6
    (stdlib) erl_eval.erl:888: :erl_eval.expr_list/6
    (stdlib) erl_eval.erl:411: :erl_eval.expr/5

iex(4)> spawn(fn -> IO.inspect :ets.lookup(t, :foo) end)
#PID<0.108.0>
[]
5 Likes

yep, give_away could work, but unfortunately it will mean that the child workers cannot concurrently modify the ets table. Also as you said, it seems like a lot of work (though probably using heir will make it a little less messy.)

Probably this is the way to go. I think the additional book-keeping here is worth it.

heir only ensure there is at least one process to inherit the table should the owning process dies. ets tables are deleted when the owning process dies. Usually we make a Supervisor a heir to avoid losing the table. I don’t see how it would make it less messy in this situation.

Overall I doubt the use-case of individual processes updating a unique row makes sense because ets does not have the concept of row locking.

The way I was thinking about it was that the spawning process (spawned using Task.async_stream/3) would inherit the table and on shutdown, would return the table to the heir (which would be the process where Task.async_stream/3 is called).

My use case is more contrived, I have a list of keys to update in the table, all of which will be unique. Then, according to this article, I should be able to see up to 16 concurrent write operations if I enable write concurrency, given a good distribution of key hashes.

If the spawning process inherits the table won’t that make it the only process which can write to the table forcing a serialization point ?

I think you just got yourself a nice load testing project :smiley:

At this point I’m not convince you are getting much gains over:

  1. Serializing all writes through one GenServer or
  2. Just making ets table public and allowing the child processes to write directly to it

You make (2) a bit more protected by not naming the table and passing the Tid down to the child processes.

1 Like

If the spawning process inherits the table won’t that make it the only process which can write to the table forcing a serialization point ?

Yep exactly, which is why the give_away solution might not work as well. I think @alco and your last point of using unnamed public tables is the way to go.