Skip to content

Commit 66440f9

Browse files
Merge pull request #5697 from BOINC/dpa_replica
PHP DB code: clean up the logic, and allow for > 1 readonly replica
2 parents 8ae0805 + da1727c commit 66440f9

File tree

3 files changed

+136
-211
lines changed

3 files changed

+136
-211
lines changed

html/inc/boinc_db.inc

Lines changed: 93 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
// This file is part of BOINC.
3-
// http://boinc.berkeley.edu
4-
// Copyright (C) 2008 University of California
3+
// https://boinc.berkeley.edu
4+
// Copyright (C) 2024 University of California
55
//
66
// BOINC is free software; you can redistribute it and/or modify it
77
// under the terms of the GNU Lesser General Public License
@@ -16,6 +16,19 @@
1616
// You should have received a copy of the GNU Lesser General Public License
1717
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
1818

19+
// A project can have one or more BOINC databases:
20+
// DB 0:
21+
// the main DB; read/write
22+
// identified in config file by db_host, db_name, db_user, db_passwd
23+
// db_host defaults to localhost
24+
// DB 1:
25+
// read-only replica; identified by
26+
// replica_db_host/name/user/passwd (must include all)
27+
// DB 2:
28+
// read-only replica; identified by
29+
// replica2_db_host/name/user/passwd (must include all)
30+
// ... and potentially more
31+
1932
function incs() {
2033
$d = dirname(__FILE__);
2134
require_once("$d/db_conn.inc");
@@ -24,122 +37,102 @@ function incs() {
2437

2538
incs();
2639

27-
class BoincDb extends DbConn {
28-
static $instance;
40+
// class BoincDb represents a connection to a BOINC database.
41+
// All its members are static, so there's only 1 connection at a time.
42+
// get(n) establishes a connection to DB n,
43+
// or DB 0 if that fails or doesn't exit.
44+
// close() closes the connection.
2945

30-
// connect to the database (possibly to a read-only replica)
31-
// NOTE: choice of replica can be made only at the page level.
32-
// If there's a page that's guaranteed to do only reads, put
33-
// BoincDb::get(true);
34-
// at the top of it.
35-
//
36-
// Specify a $fallback_mode that is used when $readonly is true:
37-
// 0: default, use db_user if no replica_db_user is specified,
38-
// first try replica_db_host (if specified) then db_host
39-
// 1: only use replica_db_user, first try replica_db_host then db_host
40-
// 2: only use replica_db_user, only try replica_db_host
41-
// can be set projectwide using <replica_fallback_mode>
46+
class BoincDb {
47+
static $instance; // a DbConn object, or null
48+
static $dbnum; // which replica we're connected to
49+
50+
// connect to DB $dbnum (0, 1, ...)
51+
// If the requested DB doesn't exist or connection fails, connect to DB 0.
52+
// Set self::$instance; no return value
4253
//
43-
static function get_aux($readonly, $fallback_mode = 0) {
44-
$config = get_config();
45-
$user = parse_config($config, '<db_user>');
46-
$passwd = parse_config($config, '<db_passwd>');
47-
$host = parse_config($config, '<db_host>');
48-
$replica_host = parse_config($config, '<replica_db_host>');
49-
$name = parse_config($config, '<db_name>');
50-
$fm = parse_config($config, '<replica_fallback_mode>');
51-
if ($fm) {
52-
// override parameter with config.xml setting
53-
$fallback_mode = $fm;
54-
}
55-
if ($host == null) {
56-
$host = "localhost";
57-
}
54+
static function get_aux($dbnum) {
5855
$instance = new DbConn();
59-
if ($readonly) {
60-
if (($fallback_mode > 0) && (!$replica_host)) {
61-
error_log("BoincDb::get_aux(): <replica_db_host> required for \$fallback_mode > 0 (giving up)");
62-
$instance = null;
63-
self::$instance = $instance;
64-
return $instance;
65-
}
66-
$u = parse_config($config, '<replica_db_user>');
67-
$p = parse_config($config, '<replica_db_passwd>');
68-
$n = parse_config($config, '<replica_db_name>');
69-
if (($fallback_mode > 0) && (!$u || !$p || !$n)) {
70-
error_log("BoincDb::get_aux(): <replica_db_*> required for \$fallback_mode > 0 (giving up)");
71-
$instance = null;
72-
self::$instance = $instance;
73-
return $instance;
74-
} else {
75-
// use replica user if given or use normal user for $fallback_mode == 0
76-
if ($u) $user = $u;
77-
if ($p) $passwd = $p;
78-
if ($n) $name = $n;
79-
}
80-
// skip this block if no $replica_host is specified for $fallback_mode == 0
81-
if ($replica_host) {
82-
$retval = $instance->init_conn(
83-
$user, $passwd, $replica_host, $name, true
84-
);
56+
self::$instance = null;
57+
$config = get_config();
58+
if ($dbnum) {
59+
$r = $dbnum==1?'':strval($dbnum);
60+
$host = parse_config($config, sprintf('<replica%s_db_host>', $r));
61+
$name = parse_config($config, sprintf('<replica%s_db_name>', $r));
62+
$user = parse_config($config, sprintf('<replica%s_db_user>', $r));
63+
$passwd = parse_config($config, sprintf('<replica%s_db_passwd>', $r));
64+
if ($host && $name && $user && $passwd) {
65+
$retval = $instance->init_conn($user, $passwd, $host, $name);
8566
if ($retval) {
86-
// needed for places where we do direct queries
87-
if (!$instance->do_query("use $name")) {
88-
error_log("BoincDb::get_aux(): Couldn't select database $name on $replica_host (giving up)");
89-
$instance = null;
90-
}
91-
self::$instance = $instance;
92-
return $instance;
93-
} elseif ($fallback_mode == 2) {
94-
// no fallback to master in this case
95-
error_log("BoincDb::get_aux(): Couldn't connect to $user@$replica_host (giving up)");
96-
$instance = null;
67+
//error_log("BoincDb::get_aux(): connected to replica DB $dbnum");
9768
self::$instance = $instance;
98-
return $instance;
99-
} else {
100-
error_log("BoincDb::get_aux(): Couldn't connect to $user@$replica_host (trying $user@$host next)");
69+
self::$dbnum = $dbnum;
70+
return;
10171
}
10272
}
73+
// if can't connect to replica, fall through and try DB 0
10374
}
104-
$retval = $instance->init_conn($user, $passwd, $host, $name, false);
105-
if (!$retval) {
106-
$instance = null;
107-
error_log("BoincDb::get_aux(): Couldn't connect to $user@$host (giving up)");
108-
} else {
109-
// needed for places where we do direct queries
110-
if (!$instance->do_query("use $name")) {
111-
error_log("BoincDb::get_aux(): Couldn't select database $name on $host (giving up)");
112-
$instance = null;
113-
}
75+
$host = parse_config($config, '<db_host>');
76+
if (!$host) $host = 'localhost';
77+
$user = parse_config($config, '<db_user>');
78+
$name = parse_config($config, '<db_name>');
79+
$passwd = parse_config($config, '<db_passwd>');
80+
if (!$name || !$user || !$passwd) {
81+
error_log("BoincDb::get_aux(): must specify DB name, user, passwd");
82+
return;
83+
}
84+
$retval = $instance->init_conn($user, $passwd, $host, $name);
85+
if ($retval) {
86+
//error_log("BoincDb::get_aux(): connected to DB $dbnum");
87+
self::$instance = $instance;
88+
self::$dbnum = 0;
89+
return;
11490
}
115-
self::$instance = $instance;
116-
return $instance;
91+
error_log("BoincDb::get_aux(): Couldn't connect to DB $dbnum");
11792
}
11893

119-
// same, but
94+
// connect to DB $dbnum, but first:
12095
// 1) check for a cached connection
12196
// 2) check whether the "stop_web" trigger file is present
12297
//
123-
static function get($readonly = false, $fallback_mode = 0) {
98+
// If there's a page that's guaranteed to do only reads, put
99+
// BoincDb::get(true);
100+
// at the top of it.
101+
//
102+
// Note: true == 1.
103+
// You can also use 2, 3... to select other replicas
104+
//
105+
static function get($dbnum = 0) {
124106
global $generating_xml;
125-
if (!isset(self::$instance)) {
126-
if (web_stopped()) {
127-
if ($generating_xml) {
128-
xml_error(-183, "project down for maintenance");
129-
} else {
130-
show_project_down();
131-
}
107+
if (isset(self::$instance)) {
108+
if (self::$dbnum == $dbnum) {
109+
return self::$instance;
132110
}
133-
self::get_aux($readonly, $fallback_mode);
134-
if (!self::$instance) {
135-
if ($generating_xml) {
136-
xml_error(-138, "Can't connect to database");
137-
} else {
138-
error_page("Can't connect to database");
139-
}
111+
close();
112+
}
113+
if (web_stopped()) {
114+
if ($generating_xml) {
115+
xml_error(-183, "project down for maintenance");
116+
} else {
117+
show_project_down();
140118
}
141119
}
142-
return self::$instance;
120+
self::get_aux($dbnum);
121+
if (self::$instance) {
122+
return self::$instance;
123+
}
124+
if ($generating_xml) {
125+
xml_error(-138, "Can't connect to database");
126+
} else {
127+
error_page("Can't connect to database");
128+
}
129+
}
130+
131+
static function close() {
132+
if (isset(self::$instance)) {
133+
self::$instance->close();
134+
self::$instance = null;
135+
}
143136
}
144137

145138
static function escape_string($string) {

html/inc/db.inc

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,7 @@ require_once("../inc/util_basic.inc");
2222
// When possible, use the classes in boinc_db.inc instead.
2323
// Lots of old code uses these functions, e.g. in ops/
2424

25-
// use mysqli if available,
26-
// but let projects not use it if they want
27-
// (put <no_mysqli/> in config.xml)
28-
//
29-
if (parse_bool(get_config(), "no_mysqli")) {
30-
define("MYSQLI", false);
31-
} else {
32-
if (class_exists("mysqli")) {
33-
define("MYSQLI", true);
34-
$mysqli = null;
35-
} else {
36-
define("MYSQLI", false);
37-
}
38-
}
25+
define("MYSQLI", true);
3926

4027
if (MYSQLI) {
4128
function _mysql_connect($host, $user, $pass, $dbname) {

0 commit comments

Comments
 (0)