Merge lp:~clint-fewbar/drizzle/regex-policy-cache-limiter into lp:~drizzle-trunk/drizzle/development

Proposed by Monty Taylor
Status: Merged
Merged at revision: 2535
Proposed branch: lp:~clint-fewbar/drizzle/regex-policy-cache-limiter
Merge into: lp:~drizzle-trunk/drizzle/development
Diff against target: 480 lines (+291/-42)
5 files modified
plugin/regex_policy/module.cc (+117/-33)
plugin/regex_policy/policy.h (+30/-9)
plugin/regex_policy/tests/r/cache_overflow.result (+73/-0)
plugin/regex_policy/tests/t/cache_overflow-master.opt (+1/-0)
plugin/regex_policy/tests/t/cache_overflow.test (+70/-0)
To merge this branch: bzr merge lp:~clint-fewbar/drizzle/regex-policy-cache-limiter
Reviewer Review Type Date Requested Status
Olaf van der Spek (community) Needs Fixing
Drizzle Developers Pending
Lee Bieber Pending
Review via email: mp+53536@code.launchpad.net

This proposal supersedes a proposal from 2011-03-09.

Description of the change

The version of regex_policy plugin in trunk will eat memory indefinitely, adding an item for every user+object combination ever checked. This adds a limit on the number of items the cache will hold.

To post a comment you must log in.
Revision history for this message
Olaf van der Spek (olafvdspek) wrote : Posted in a previous version of this proposal

AFAIK there's a format specifier for size_t.

review: Needs Fixing
Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

Olaf.. can you share what it is?

%zu and %zd are both forbidden in ISO C++.

Revision history for this message
Olaf van der Spek (olafvdspek) wrote : Posted in a previous version of this proposal

On Thu, Mar 10, 2011 at 6:34 PM, Clint Byrum <email address hidden> wrote:
> Olaf.. can you share what it is?
>
> %zu and %zd are both forbidden in ISO C++.

I was thinking about those two.

Revision history for this message
Monty Taylor (mordred) wrote : Posted in a previous version of this proposal
Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

Can we try one more time please?

Revision history for this message
Olaf van der Spek (olafvdspek) wrote : Posted in a previous version of this proposal

194: lru.empty();
Should this be clear()?

Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

On Fri, 2011-03-11 at 10:45 +0000, Olaf van der Spek wrote:
> 194: lru.empty();
> Should this be clear()?

Yes! good catch. The affect of this would have been poor performance
(the list is limited in size, but at this point in the code, its better
to have the list empty because the cache has already had its entries
pruned appropriately).

Pushed the fix.

Revision history for this message
Lee Bieber (kalebral-deactivatedaccount) wrote : Posted in a previous version of this proposal

Test failures - http://jenkins.drizzle.org/job/drizzle-build-repeat-tests-twice/329/

also on freebsd, OSX and opensue

review: Needs Fixing
Revision history for this message
Lee Bieber (kalebral-deactivatedaccount) wrote : Posted in a previous version of this proposal

Correction - not freebsd, that was an unrelated failure

Logfile for opensuse - http://jenkins.drizzle.org/job/drizzle-build-opensuse-64bit/1422/console
Logfile for OSX - http://jenkins.drizzle.org/job/drizzle-build-hades/1316/console (note that slave.basic test failure is a different issue)

review: Needs Fixing
Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

Ugh, the opensuse and osx failures are probably due to the boost::unordered_map valgrind warnings about writing to freed memory... may require deeper investigation.

I did fix the "build repeat tests twice" problem though. I'll leave this as work in progress until I can fully understand the other issue.. hopefully by Monday.

Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

I was able to refactor out the [] overload of CacheMap which I believe was the culprit, causing me to return a reference to freed memory which subsequently would be overwritten.

Revision history for this message
Olaf van der Spek (olafvdspek) wrote : Posted in a previous version of this proposal

On Mon, Mar 14, 2011 at 3:04 PM, Clint Byrum <email address hidden> wrote:
>   Policy *policy= new (nothrow) Policy(fs::path(vm["policy"].as<string>()));
>   if (policy == NULL or not policy->loadFile())

(nothrow) and policy == NULL are't needed (AFAIK).

> +  UnorderedCheckMap::iterator check_val;

Try to combine declaration and initialization of vars.

> +    lock.unlock();

Not needed

> -  CheckMap *new_cache;

Declare -> init

> -  boost::mutex::scoped_lock lock(check_cache_mutex, boost::defer_lock);
> -  lock.lock();
> -
> -  // Copy the current one
> -  if (*check_cache)
> -  {
> -    new_cache= new CheckMap(**check_cache);
> -  }
> -  else
> -  {
> -    new_cache= new CheckMap();
> -  }

? : operator is a better match here.

> -  if (old_cache)

No need for the if.

> -  {
> -    delete old_cache;
> -  }

Greetings,

Olaf

Revision history for this message
Clint Byrum (clint-fewbar) wrote : Posted in a previous version of this proposal

Olaf, are you reading the current merge proposal?

Most of the things you're suggesting I change are completely removed from the code.

To respond to the stuff that is still there:

nothrow and NULL check were the way I was told to avoid exception handling early on in drizzle's lifecycle when exceptions weren't used at all. I'll fix that in the next round of changes.

I've been explicit about calling lock.unlock() because I want to *see* the unlocks.

Revision history for this message
Olaf van der Spek (olafvdspek) wrote : Posted in a previous version of this proposal

On Tue, Mar 15, 2011 at 7:23 AM, Clint Byrum <email address hidden> wrote:
> Olaf, are you reading the current merge proposal?

Oops. I replied to the lines with - instead of with + :p

--
Olaf

Revision history for this message
Olaf van der Spek (olafvdspek) wrote :

> UnorderedCheckMap::iterator CheckMap::find(std::string const &k)

Why return an iterator? If an iterator isn't required, a pointer (to the value) would be better.

review: Needs Fixing
Revision history for this message
Brian Aker (brianaker) wrote :

I am now looking at this.

Revision history for this message
Brian Aker (brianaker) wrote :

I can't get this to merge :(

Revision history for this message
Clint Byrum (clint-fewbar) wrote :

Merged with latest trunk now, so should merge into trunk cleanly for CI testing.

Revision history for this message
Brian Aker (brianaker) wrote :

Now testing!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'plugin/regex_policy/module.cc'
2--- plugin/regex_policy/module.cc 2012-01-15 20:54:59 +0000
3+++ plugin/regex_policy/module.cc 2012-03-16 16:56:20 +0000
4@@ -23,6 +23,9 @@
5
6 #include <config.h>
7
8+#include <boost/unordered_set.hpp>
9+#include <boost/thread/locks.hpp>
10+
11 #include <drizzled/plugin/authorization.h>
12 #include <drizzled/module/option_map.h>
13
14@@ -38,10 +41,25 @@
15 namespace regex_policy
16 {
17
18+uint64_t max_cache_buckets= DEFAULT_MAX_CACHE_BUCKETS;
19+uint64_t max_lru_length= DEFAULT_MAX_LRU_LENGTH;
20+
21 static int init(module::Context &context)
22 {
23 const module::option_map &vm= context.getOptions();
24
25+ max_cache_buckets= vm["max-cache-buckets"].as<uint64_t>();
26+ if (max_cache_buckets < 1)
27+ {
28+ errmsg_printf(error::ERROR, _("max-cache-buckets is too low, must be greater than 0"));
29+ return 1;
30+ }
31+ max_lru_length= vm["max-lru-length"].as<uint64_t>();
32+ if (max_lru_length < 1)
33+ {
34+ errmsg_printf(error::ERROR, _("max-lru-length is too low, must be greater than 0"));
35+ return 1;
36+ }
37 Policy *policy= new Policy(fs::path(vm["policy"].as<string>()));
38 if (not policy->loadFile())
39 {
40@@ -62,6 +80,12 @@
41 context("policy",
42 po::value<string>()->default_value(DEFAULT_POLICY_FILE.string()),
43 N_("File to load for regex authorization policies"));
44+ context("max-cache-buckets",
45+ po::value<uint64_t>()->default_value(DEFAULT_MAX_CACHE_BUCKETS),
46+ N_("Maximum buckets for authorization cache"));
47+ context("max-lru-length",
48+ po::value<uint64_t>()->default_value(DEFAULT_MAX_LRU_LENGTH),
49+ N_("Maximum number of LRU entries to track at once"));
50 }
51
52 bool Policy::loadFile()
53@@ -169,14 +193,11 @@
54 clearPolicyItemList(table_policies);
55 clearPolicyItemList(process_policies);
56 clearPolicyItemList(schema_policies);
57- delete table_check_cache;
58- delete process_check_cache;
59- delete schema_check_cache;
60 }
61
62 bool Policy::restrictObject(const drizzled::identifier::User &user_ctx,
63 const string &obj, const PolicyItemList &policies,
64- CheckMap **check_cache)
65+ CheckMap &check_cache)
66 {
67 CheckItem c(user_ctx.username(), obj, check_cache);
68 if (!c.hasCachedResult())
69@@ -198,19 +219,19 @@
70 bool Policy::restrictSchema(const drizzled::identifier::User &user_ctx,
71 const drizzled::identifier::Schema& schema)
72 {
73- return restrictObject(user_ctx, schema.getSchemaName(), schema_policies, &schema_check_cache);
74+ return restrictObject(user_ctx, schema.getSchemaName(), schema_policies, schema_check_cache);
75 }
76
77 bool Policy::restrictProcess(const drizzled::identifier::User &user_ctx,
78 const drizzled::identifier::User &session_ctx)
79 {
80- return restrictObject(user_ctx, session_ctx.username(), process_policies, &process_check_cache);
81+ return restrictObject(user_ctx, session_ctx.username(), process_policies, process_check_cache);
82 }
83
84 bool Policy::restrictTable(const drizzled::identifier::User& user_ctx,
85 const drizzled::identifier::Table& table)
86 {
87- return restrictObject(user_ctx, table.getTableName(), table_policies, &table_check_cache);
88+ return restrictObject(user_ctx, table.getTableName(), table_policies, table_check_cache);
89 }
90
91 bool CheckItem::operator()(PolicyItem *p)
92@@ -234,41 +255,104 @@
93 return false;
94 }
95
96-CheckItem::CheckItem(const std::string &user_in, const std::string &obj_in, CheckMap **check_cache_in)
97+CheckItem::CheckItem(const std::string &user_in, const std::string &obj_in, CheckMap &check_cache_in)
98 : user(user_in), object(obj_in), has_cached_result(false), check_cache(check_cache_in)
99 {
100- CheckMap::iterator check_val;
101- std::stringstream keystream;
102- keystream << user << "_" << object;
103- key= keystream.str();
104-
105- /* using RCU to only need to lock when updating the cache */
106- if ((*check_cache) && (check_val= (*check_cache)->find(key)) != (*check_cache)->end())
107- {
108- setCachedResult(check_val->second);
109+ UnorderedCheckMap::iterator check_val;
110+ key= user + "_" + object;
111+
112+ if ((check_val= check_cache.find(key)) != check_cache.end())
113+ {
114+ /* It was in the cache, no need to do any more lookups */
115+ cached_result= check_val->second;
116+ has_cached_result= true;
117+ }
118+}
119+
120+UnorderedCheckMap::iterator CheckMap::find(std::string const &k)
121+{
122+ /* tack on to LRU list */
123+ boost::mutex::scoped_lock lock(lru_mutex);
124+ lru.push_back(k);
125+ if (lru.size() > max_lru_length)
126+ {
127+ /* Fold all of the oldest entries into a single list at the front */
128+ uint64_t size_halfway= lru.size() / 2;
129+ LruList::iterator halfway= lru.begin();
130+ halfway += size_halfway;
131+ boost::unordered_set<std::string> uniqs;
132+ uniqs.insert(lru.begin(), halfway);
133+
134+ /* If we can save space, drop the oldest half */
135+ if (size_halfway < uniqs.size())
136+ {
137+ lru.erase(lru.begin(), halfway);
138+
139+ /* Re-add set elements to front */
140+ lru.insert(lru.begin(), uniqs.begin(), uniqs.end());
141+ }
142+ }
143+ lock.unlock();
144+ boost::shared_lock<boost::shared_mutex> map_lock(map_mutex);
145+ return map.find(k);
146+}
147+
148+void CheckMap::insert(std::string const &k, bool v)
149+{
150+ boost::unique_lock<boost::shared_mutex> map_lock(map_mutex);
151+ /* add our new hotness to the map */
152+ map[k]=v;
153+ /* Now prune if necessary */
154+ if (map.bucket_count() > max_cache_buckets)
155+ {
156+ /* Determine LRU key by running through the LRU list */
157+ boost::unordered_set<std::string> found;
158+
159+ /* Must unfortunately lock the LRU list while we traverse it */
160+ boost::mutex::scoped_lock lock(lru_mutex);
161+ for (LruList::reverse_iterator x= lru.rbegin(); x < lru.rend(); ++x)
162+ {
163+ if (found.find(*x) == found.end())
164+ {
165+ /* Newly found key */
166+ if (found.size() >= max_cache_buckets)
167+ {
168+ /* Since found is already as big as the cache can be, anything else
169+ is LRU */
170+ map.erase(*x);
171+ }
172+ else
173+ {
174+ found.insert(*x);
175+ }
176+ }
177+ }
178+ if (map.bucket_count() > max_cache_buckets)
179+ {
180+ /* Still too big. */
181+ if (lru.size())
182+ {
183+ /* Just delete the oldest item */
184+ map.erase(*(lru.begin()));
185+ }
186+ else
187+ {
188+ /* Nothing to delete, warn */
189+ errmsg_printf(error::WARN,
190+ _("Unable to reduce size of cache below max buckets (current buckets=%" PRIu64 ")"),
191+ static_cast<uint64_t>(map.bucket_count()));
192+ }
193+ }
194+ lru.clear();
195+ lock.unlock();
196 }
197 }
198
199 void CheckItem::setCachedResult(bool result)
200 {
201- // TODO: make the mutex per-cache
202- boost::mutex::scoped_lock lock(check_cache_mutex, boost::defer_lock);
203- lock.lock();
204-
205- // Copy the current one
206- CheckMap* new_cache= *check_cache ? new CheckMap(**check_cache) : new CheckMap;
207-
208- // Update it
209- (*new_cache)[key]= result;
210- // Replace old
211- CheckMap* old_cache= *check_cache;
212- *check_cache= new_cache;
213-
214- lock.unlock();
215+ check_cache.insert(key, result);
216 has_cached_result= true;
217 cached_result= result;
218-
219- delete old_cache;
220 }
221
222 } /* namespace regex_policy */
223
224=== modified file 'plugin/regex_policy/policy.h'
225--- plugin/regex_policy/policy.h 2012-01-16 02:37:54 +0000
226+++ plugin/regex_policy/policy.h 2012-03-16 16:56:20 +0000
227@@ -29,6 +29,8 @@
228 #include <boost/regex.hpp>
229 #include <boost/unordered_map.hpp>
230 #include <boost/thread/mutex.hpp>
231+#include <boost/thread/shared_mutex.hpp>
232+#include <boost/thread/locks.hpp>
233
234 #include <drizzled/configmake.h>
235 #include <drizzled/plugin/authorization.h>
236@@ -39,6 +41,9 @@
237
238 static const fs::path DEFAULT_POLICY_FILE= SYSCONFDIR "/drizzle.policy";
239
240+static const uint64_t DEFAULT_MAX_LRU_LENGTH= 16384;
241+static const uint64_t DEFAULT_MAX_CACHE_BUCKETS= 4096;
242+
243 static const char *comment_regex = "^[[:space:]]*#.*$";
244 static const char *empty_regex = "^[[:space:]]*$";
245 static const char *table_match_regex = "^([^ ]+) table\\=([^ ]+) (ACCEPT|DENY)$";
246@@ -49,6 +54,7 @@
247 static const int MATCH_REGEX_OBJECT_POS= 2;
248 static const int MATCH_REGEX_ACTION_POS= 3;
249
250+
251 typedef enum
252 {
253 POLICY_ACCEPT,
254@@ -100,9 +106,23 @@
255 };
256
257 typedef std::list<PolicyItem *> PolicyItemList;
258-typedef boost::unordered_map<std::string, bool> CheckMap;
259+typedef std::vector<std::string> LruList;
260+typedef boost::unordered_map<std::string, bool> UnorderedCheckMap;
261
262-static boost::mutex check_cache_mutex;
263+class CheckMap
264+{
265+ LruList lru;
266+ boost::mutex lru_mutex;
267+ boost::shared_mutex map_mutex;
268+ UnorderedCheckMap map;
269+public:
270+ UnorderedCheckMap::iterator find(std::string const&k);
271+ UnorderedCheckMap::const_iterator end() const
272+ {
273+ return map.end();
274+ }
275+ void insert(std::string const &k, bool v);
276+};
277
278 class CheckItem
279 {
280@@ -111,9 +131,9 @@
281 std::string key;
282 bool has_cached_result;
283 bool cached_result;
284- CheckMap **check_cache;
285+ CheckMap &check_cache;
286 public:
287- CheckItem(const std::string &u, const std::string &obj, CheckMap **check_cache);
288+ CheckItem(const std::string &u, const std::string &obj, CheckMap &check_cache);
289 bool operator()(PolicyItem *p);
290 bool hasCachedResult() const
291 {
292@@ -149,7 +169,7 @@
293 public:
294 Policy(const fs::path &f_path) :
295 drizzled::plugin::Authorization("regex_policy"), policy_file(f_path), error(),
296- table_check_cache(NULL), schema_check_cache(NULL), process_check_cache(NULL)
297+ table_check_cache(), schema_check_cache(), process_check_cache()
298 { }
299
300 virtual bool restrictSchema(const drizzled::identifier::User &user_ctx,
301@@ -167,15 +187,16 @@
302 private:
303 bool restrictObject(const drizzled::identifier::User &user_ctx,
304 const std::string &obj, const PolicyItemList &policies,
305- CheckMap **check_cache);
306+ CheckMap &check_cache);
307 fs::path policy_file;
308+
309 std::stringstream error;
310 PolicyItemList table_policies;
311 PolicyItemList schema_policies;
312 PolicyItemList process_policies;
313- CheckMap *table_check_cache;
314- CheckMap *schema_check_cache;
315- CheckMap *process_check_cache;
316+ CheckMap table_check_cache;
317+ CheckMap schema_check_cache;
318+ CheckMap process_check_cache;
319 };
320
321 } /* namespace regex_policy */
322
323=== added file 'plugin/regex_policy/tests/r/cache_overflow.result'
324--- plugin/regex_policy/tests/r/cache_overflow.result 1970-01-01 00:00:00 +0000
325+++ plugin/regex_policy/tests/r/cache_overflow.result 2012-03-16 16:56:20 +0000
326@@ -0,0 +1,73 @@
327+create schema user0;
328+create schema user1;
329+create schema user2;
330+create schema user3;
331+create schema user4;
332+create schema user5;
333+create schema user6;
334+create schema user7;
335+SELECT SCHEMA_NAME FROM DATA_DICTIONARY.SCHEMAS ORDER BY SCHEMA_NAME;
336+SCHEMA_NAME
337+DATA_DICTIONARY
338+INFORMATION_SCHEMA
339+mysql
340+test
341+user0
342+user1
343+user2
344+user3
345+user4
346+user5
347+user6
348+user7
349+use user0;
350+create table t1 (id int);
351+insert into t1 values(1);
352+create table user1.t1 as select * from user0.t1;
353+create table user2.t1 as select * from user0.t1;
354+create table user3.t1 as select * from user0.t1;
355+create table user4.t1 as select * from user0.t1;
356+create table user5.t1 as select * from user0.t1;
357+create table user6.t1 as select * from user0.t1;
358+create table user7.t1 as select * from user0.t1;
359+SELECT * from user1.dont_exist0;
360+ERROR 42S02: Unknown table 'user1.dont_exist0'
361+SELECT * from user1.dont_exist1;
362+ERROR 42S02: Unknown table 'user1.dont_exist1'
363+SELECT * from user1.dont_exist2;
364+ERROR 42S02: Unknown table 'user1.dont_exist2'
365+SELECT * from user1.dont_exist3;
366+ERROR 42S02: Unknown table 'user1.dont_exist3'
367+SELECT * from user1.dont_exist4;
368+ERROR 42S02: Unknown table 'user1.dont_exist4'
369+SELECT * from user1.dont_exist5;
370+ERROR 42S02: Unknown table 'user1.dont_exist5'
371+SELECT * from user1.dont_exist6;
372+ERROR 42S02: Unknown table 'user1.dont_exist6'
373+SELECT * from user1.dont_exist7;
374+ERROR 42S02: Unknown table 'user1.dont_exist7'
375+SELECT * from user1.t1;
376+id
377+1
378+SELECT * from user0.t1;
379+ERROR 42000: Access denied for user 'user1' to schema 'user0'
380+SELECT * from user2.t1;
381+ERROR 42000: Access denied for user 'user1' to schema 'user2'
382+SELECT * from user3.t1;
383+ERROR 42000: Access denied for user 'user1' to schema 'user3'
384+SELECT * from user4.t1;
385+ERROR 42000: Access denied for user 'user1' to schema 'user4'
386+SELECT * from user5.t1;
387+ERROR 42000: Access denied for user 'user1' to schema 'user5'
388+SELECT * from user6.t1;
389+ERROR 42000: Access denied for user 'user1' to schema 'user6'
390+SELECT * from user7.t1;
391+ERROR 42000: Access denied for user 'user1' to schema 'user7'
392+drop schema user0;
393+drop schema user1;
394+drop schema user2;
395+drop schema user3;
396+drop schema user4;
397+drop schema user5;
398+drop schema user6;
399+drop schema user7;
400
401=== added file 'plugin/regex_policy/tests/t/cache_overflow-master.opt'
402--- plugin/regex_policy/tests/t/cache_overflow-master.opt 1970-01-01 00:00:00 +0000
403+++ plugin/regex_policy/tests/t/cache_overflow-master.opt 2012-03-16 16:56:20 +0000
404@@ -0,0 +1,1 @@
405+--plugin-add=regex_policy --regex-policy.policy=$TOP_SRCDIR/plugin/regex_policy/tests/t/basic.policy --verbose=INSPECT --regex-policy.max-cache-buckets=5 --regex-policy.max-lru-length=7
406
407=== added file 'plugin/regex_policy/tests/t/cache_overflow.test'
408--- plugin/regex_policy/tests/t/cache_overflow.test 1970-01-01 00:00:00 +0000
409+++ plugin/regex_policy/tests/t/cache_overflow.test 2012-03-16 16:56:20 +0000
410@@ -0,0 +1,70 @@
411+# Check for error if no parameter provided
412+create schema user0;
413+create schema user1;
414+create schema user2;
415+create schema user3;
416+create schema user4;
417+create schema user5;
418+create schema user6;
419+create schema user7;
420+SELECT SCHEMA_NAME FROM DATA_DICTIONARY.SCHEMAS ORDER BY SCHEMA_NAME;
421+
422+# Set up a table to be able to test not being able to kill other people
423+use user0;
424+create table t1 (id int);
425+insert into t1 values(1);
426+create table user1.t1 as select * from user0.t1;
427+create table user2.t1 as select * from user0.t1;
428+create table user3.t1 as select * from user0.t1;
429+create table user4.t1 as select * from user0.t1;
430+create table user5.t1 as select * from user0.t1;
431+create table user6.t1 as select * from user0.t1;
432+create table user7.t1 as select * from user0.t1;
433+
434+connect (should_succeed,localhost,user1,,user1,,);
435+connection should_succeed;
436+# Try selecting from 8 tables that don't exist
437+--error ER_TABLE_UNKNOWN
438+SELECT * from user1.dont_exist0;
439+--error ER_TABLE_UNKNOWN
440+SELECT * from user1.dont_exist1;
441+--error ER_TABLE_UNKNOWN
442+SELECT * from user1.dont_exist2;
443+--error ER_TABLE_UNKNOWN
444+SELECT * from user1.dont_exist3;
445+--error ER_TABLE_UNKNOWN
446+SELECT * from user1.dont_exist4;
447+--error ER_TABLE_UNKNOWN
448+SELECT * from user1.dont_exist5;
449+--error ER_TABLE_UNKNOWN
450+SELECT * from user1.dont_exist6;
451+--error ER_TABLE_UNKNOWN
452+SELECT * from user1.dont_exist7;
453+
454+# Try accessing 8 different existing schemas/tables
455+SELECT * from user1.t1;
456+--error ER_DBACCESS_DENIED_ERROR
457+SELECT * from user0.t1;
458+--error ER_DBACCESS_DENIED_ERROR
459+SELECT * from user2.t1;
460+--error ER_DBACCESS_DENIED_ERROR
461+SELECT * from user3.t1;
462+--error ER_DBACCESS_DENIED_ERROR
463+SELECT * from user4.t1;
464+--error ER_DBACCESS_DENIED_ERROR
465+SELECT * from user5.t1;
466+--error ER_DBACCESS_DENIED_ERROR
467+SELECT * from user6.t1;
468+--error ER_DBACCESS_DENIED_ERROR
469+SELECT * from user7.t1;
470+
471+connection default;
472+drop schema user0;
473+drop schema user1;
474+drop schema user2;
475+drop schema user3;
476+drop schema user4;
477+drop schema user5;
478+drop schema user6;
479+drop schema user7;
480+disconnect should_succeed;