Небезопасно удалять элементы из связанного хеша DB_File при повторении с `each`?
Эта проблема
Я использую NetBSD 6.1, Perl v5.18.1 и DB_File v1.818. Если я перебираю хеш, связанный с DB_File, используя each
и удалить каждый элемент из хэша, не все элементы будут удалены. Вот скрипт, демонстрирующий проблему:
use strict;
use warnings;
use DB_File;
my $dbfile = "/tmp/foo.db";
! -f $dbfile or unlink($dbfile) or die("unable to delete $dbfile");
my %db;
tie(%db, "DB_File", "/tmp/foo.db", O_RDWR|O_CREAT, 0644);
# add some random records
my @chars = ("0".."9", "a".."f");
for (1..10) {
my ($key, $val);
$key .= $chars[rand(@chars)] for 1..10;
$val .= $chars[rand(@chars)] for 1..32;
$db{$key} = $val;
}
# this doesn't delete everything from the database!
keys(%db); # reset the iterator
while (my ($key, $val) = each(%db)) {
delete $db{$key};
}
foreach (keys(%db)) {
print("\$db{$_} = $db{$_}\n");
}
untie(%db);
Когда я запускаю его, 4 (или иногда 5) из 10 записей не удаляются:
$db{4a8e5792e0} = 7a4d078a3f0f3cba750cb395fcc3343d
$db{d28e8cb226} = 17af1122f0b94113416693b1c4165954
$db{a3ae4e2e24} = 3c15270cf16601722bd8106b1727dbc2
$db{886c469eb4} = f1496f83f7866d09c9e28aae8e1b62e6
$db{2c53ebd993} = facfe8228240878aac825de4d97ca22b
Если я запускаю сценарий в системе Linux (Ubuntu 14.04), он всегда работает (все записи удалены).
Если я переключусь на foreach
зацикливание ключей, затем оно работает как в NetBSD, так и в Linux:
# this always works
foreach (keys(%db)) {
delete $db{$_};
}
Что говорит документация
Я не смог найти ничего, что ясно говорит о том, что удаление во время итерации через each
не всегда работает
Вот что мне удалось найти:
Документация для
foreach
говорит:foreach
вероятно, не будет делать то, что вы ожидаете, если VAR является связанной или другой специальной переменной.Я не уверен, что подразумевается под этим, но, как ни странно,
foreach
Дело в том, где это работает.Документация для
each
говорит:Любая вставка в хеш может изменить порядок, как и любое удаление, за исключением того, что последний ключ, возвращенный
each
или жеkeys
могут быть удалены без изменения порядка.Для меня это означает, что это безопасно
delete
текущая запись во время итерации.Документация для
DB_File
не упоминает удаление во время итерации.
Вопросы
Это проблема:
- вызвано ошибкой в реализации БД Berkeley в NetBSD?
- вызвано ошибкой в DB_File?
- известное ограничение
each
? - известное ограничение связанных хешей?
- известное ограничение связанных хешей DB_File?
Почему foreach
на клавиши работают когда each
не делает?
1 ответ
Моя догадка, что поведение является ограничением связанных хэшей. Кажется, нет никакой гарантии, что DB_File не перефразирует при удалении самого последнего извлеченного ключа.
Вы также спросили о разнице между
while (my ($key, $val) = each(%db)) {
delete $db{$key};
}
а также
foreach (keys(%db)) {
delete $db{$_};
}
,
В первом случае вы вмешиваетесь в связанный хеш, перебирая связанный хеш. Так что есть вероятность, что итератор не достигнет всех ключей.
Во втором случае вы сначала перебираете связанный хеш, чтобы получить полный список ключей. При циклическом просмотре полного списка вы гарантированно удаляете все записи.