Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
glpi
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Packages & Registries
Packages & Registries
Package Registry
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
GLPI
glpi
Commits
d89dfb02
Commit
d89dfb02
authored
Mar 04, 2019
by
Cédric Anne
Committed by
Johan Cwiklinski
Mar 08, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Check cache integrity across execution contexts
(#5465 adapted to master)
parent
512895a8
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1094 additions
and
229 deletions
+1094
-229
.gitignore
.gitignore
+1
-0
inc/config.class.php
inc/config.class.php
+1
-1
inc/html.class.php
inc/html.class.php
+1
-1
inc/session.class.php
inc/session.class.php
+1
-1
src/Glpi/Cache/CacheStorageFactory.php
src/Glpi/Cache/CacheStorageFactory.php
+8
-64
src/Glpi/Cache/SimpleCache.php
src/Glpi/Cache/SimpleCache.php
+357
-0
src/Glpi/Cache/SimpleCacheFactory.php
src/Glpi/Cache/SimpleCacheFactory.php
+194
-0
src/resources/services.yaml
src/resources/services.yaml
+9
-11
tests/GLPITestCase.php
tests/GLPITestCase.php
+9
-0
tests/bootstrap.php
tests/bootstrap.php
+3
-3
tests/circleci.parameters.yaml
tests/circleci.parameters.yaml
+1
-0
tests/functionnal/DbUtils.php
tests/functionnal/DbUtils.php
+2
-2
tests/functionnal/Entity.php
tests/functionnal/Entity.php
+2
-2
tests/parameters.yaml
tests/parameters.yaml
+0
-40
tests/travis.parameters.yaml
tests/travis.parameters.yaml
+1
-0
tests/units/Glpi/Cache/CacheStorageFactory.php
tests/units/Glpi/Cache/CacheStorageFactory.php
+0
-103
tests/units/Glpi/Cache/SimpleCache.php
tests/units/Glpi/Cache/SimpleCache.php
+294
-0
tests/units/Glpi/Cache/SimpleCacheFactory.php
tests/units/Glpi/Cache/SimpleCacheFactory.php
+209
-0
tools/cachebench.php
tools/cachebench.php
+1
-1
No files found.
.gitignore
View file @
d89dfb02
...
...
@@ -5,6 +5,7 @@
/config/parameters.yaml
/tests/config_db*
/tests/db.yaml
/tests/parameters.yaml
/plugins/
/files/
/.buildpath
...
...
inc/config.class.php
View file @
d89dfb02
...
...
@@ -1516,7 +1516,7 @@ class Config extends CommonDBTM {
function
showPerformanceInformations
()
{
global
$CONTAINER
;
$cache_storage
=
$CONTAINER
->
get
(
'application_cache
_storage'
);
$cache_storage
=
$CONTAINER
->
get
(
'application_cache
'
)
->
getStorage
(
);
if
(
!
Config
::
canUpdate
())
{
return
false
;
...
...
inc/html.class.php
View file @
d89dfb02
...
...
@@ -725,7 +725,7 @@ class Html {
static
function
displayDebugInfos
(
$with_session
=
true
,
$ajax
=
false
)
{
global
$CFG_GLPI
,
$CONTAINER
,
$DEBUG_SQL
,
$SQL_TOTAL_REQUEST
,
$SQL_TOTAL_TIMER
,
$DEBUG_AUTOLOAD
;
$cache_storage
=
$CONTAINER
->
get
(
'application_cache
_storage'
);
$cache_storage
=
$CONTAINER
->
get
(
'application_cache
'
)
->
getStorage
(
);
// Only for debug mode so not need to be translated
if
(
$_SESSION
[
'glpi_use_mode'
]
==
Session
::
DEBUG_MODE
)
{
// mode debug
...
...
inc/session.class.php
View file @
d89dfb02
...
...
@@ -624,7 +624,7 @@ class Session {
$_SESSION
[
'glpipluralnumber'
]
=
$CFG_GLPI
[
"languages"
][
$trytoload
][
5
];
}
$TRANSLATE
=
new
Zend\I18n\Translator\Translator
;
$cache_storage
=
$CONTAINER
->
get
(
'translation_cache
_storage'
);
$cache_storage
=
$CONTAINER
->
get
(
'translation_cache
'
)
->
getStorage
(
);
$TRANSLATE
->
setCache
(
$cache_storage
);
$TRANSLATE
->
addTranslationFile
(
'gettext'
,
GLPI_ROOT
.
$newfile
,
'glpi'
,
$trytoload
);
...
...
src/Glpi/Cache/CacheStorageFactory.php
View file @
d89dfb02
...
...
@@ -56,8 +56,8 @@ class CacheStorageFactory extends StorageFactory
private
static
$cacheUniqId
;
/**
* @param string $cacheDir Cache directory.
* @param string $cacheUniqId Cache unique identifier.
* @param string $cacheDir
Cache directory.
* @param string $cacheUniqId
Cache unique identifier.
*/
public
function
__construct
(
string
$cacheDir
,
string
$cacheUniqId
)
{
...
...
@@ -67,36 +67,19 @@ class CacheStorageFactory extends StorageFactory
public
static
function
factory
(
$cfg
)
{
// Compute prefered adapter if 'auto' value or no value is used
if
(
!
isset
(
$cfg
[
'adapter'
])
||
'auto'
===
$cfg
[
'adapter'
])
{
if
(
function_exists
(
'wincache_ucache_add'
))
{
$cfg
[
'adapter'
]
=
'wincache'
;
}
elseif
(
function_exists
(
'apcu_fetch'
))
{
$cfg
[
'adapter'
]
=
'apcu'
;
}
else
{
$cfg
[
'adapter'
]
=
'filesystem'
;
if
(
!
array_key_exists
(
'plugins'
,
$cfg
))
{
$cfg
[
'plugins'
]
=
[
'serializer'
];
}
elseif
(
!
in_array
(
'serializer'
,
$cfg
[
'plugins'
]))
{
$cfg
[
'plugins'
][]
=
'serializer'
;
}
}
}
// Add unique id to namespace
if
(
!
array_key_exists
(
'options'
,
$cfg
)
||
!
is_array
(
$cfg
[
'options'
]))
{
$cfg
[
'options'
]
=
[];
}
$baseNamespace
=
isset
(
$cfg
[
'options'
][
'namespace'
])
?
$cfg
[
'options'
][
'namespace'
]
:
'_default'
;
$cfg
[
'options'
][
'namespace'
]
=
$baseNamespace
.
(
empty
(
self
::
$cacheUniqId
)
?
''
:
'_'
.
self
::
$cacheUniqId
);
// Add unique id to namespace
$namespace
=
isset
(
$cfg
[
'options'
][
'namespace'
])
?
$cfg
[
'options'
][
'namespace'
]
:
'_default'
;
$namespace
.
=
(
empty
(
self
::
$cacheUniqId
)
?
''
:
'_'
.
self
::
$cacheUniqId
);
$cfg
[
'options'
][
'namespace'
]
=
$namespace
;
// Handle pathname for dba adapter
if
(
'dba'
===
$cfg
[
'adapter'
])
{
// Assign default value for pathname
if
(
!
isset
(
$cfg
[
'options'
][
'pathname'
]))
{
$namespace
=
$cfg
[
'options'
][
'namespace'
];
$cfg
[
'options'
][
'pathname'
]
=
self
::
$cacheDir
.
'/'
.
$namespace
.
'.data'
;
}
}
...
...
@@ -105,7 +88,6 @@ class CacheStorageFactory extends StorageFactory
if
(
'filesystem'
===
$cfg
[
'adapter'
])
{
// Assign default value for cache dir
if
(
!
isset
(
$cfg
[
'options'
][
'cache_dir'
]))
{
$namespace
=
$cfg
[
'options'
][
'namespace'
];
$cfg
[
'options'
][
'cache_dir'
]
=
self
::
$cacheDir
.
'/'
.
$namespace
;
}
...
...
@@ -130,44 +112,6 @@ class CacheStorageFactory extends StorageFactory
}
}
try
{
return
parent
::
factory
(
$cfg
);
}
catch
(
\
Exception
$e
)
{
if
(
'filesystem'
!==
$cfg
[
'adapter'
])
{
// Fallback to 'filesystem' adapter
trigger_error
(
sprintf
(
'Cache adapter instantiation failed, fallback to "filesystem" adapter. Error was "%s".'
,
$e
->
getMessage
()
),
E_USER_WARNING
);
return
self
::
factory
(
[
'adapter'
=>
'filesystem'
,
'options'
=>
[
'namespace'
=>
$baseNamespace
.
'_fallback'
],
]
);
}
else
{
// Fallback to 'memory' adapter
trigger_error
(
sprintf
(
'Cache adapter instantiation failed, fallback to "memory" adapter. Error was "%s".'
,
$e
->
getMessage
()
),
E_USER_WARNING
);
return
self
::
factory
(
[
'adapter'
=>
'memory'
,
'options'
=>
[
'namespace'
=>
$baseNamespace
.
'_fallback'
],
]
);
}
}
return
parent
::
factory
(
$cfg
);
}
}
src/Glpi/Cache/SimpleCache.php
0 → 100644
View file @
d89dfb02
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2018 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace
Glpi\Cache
;
if
(
!
defined
(
'GLPI_ROOT'
))
{
die
(
"Sorry. You can't access this file directly"
);
}
use
Zend\Cache\Psr\SimpleCache\SimpleCacheDecorator
;
use
Zend\Cache\Storage\StorageInterface
;
class
SimpleCache
extends
SimpleCacheDecorator
{
/**
* Determines if footprints must be checked.
*
* @var boolean
*/
private
$check_footprints
;
/**
* Footprint file path, if existing.
*
* @var string|null
*/
private
$footprint_file
;
/**
* Footprint fallback storage used if footprint file is not available.
*
* @var array
*/
private
$footprint_fallback_storage
=
[];
/**
*
* @var StorageInterface
*/
private
$storage
;
public
function
__construct
(
StorageInterface
$storage
,
$cache_dir
,
$check_footprints
=
true
)
{
parent
::
__construct
(
$storage
);
$this
->
storage
=
$storage
;
$this
->
check_footprints
=
$check_footprints
;
if
(
$this
->
check_footprints
)
{
$this
->
footprint_file
=
$cache_dir
.
'/'
.
$storage
->
getOptions
()
->
getNamespace
()
.
'.json'
;
$this
->
checkFootprintFileIntegrity
();
}
}
public
function
get
(
$key
,
$default
=
null
)
{
$cached_value
=
parent
::
get
(
$key
,
$default
);
if
(
!
$this
->
check_footprints
)
{
return
$cached_value
;
}
if
(
$this
->
getCachedFootprint
(
$key
)
!==
$this
->
computeFootprint
(
$cached_value
))
{
// If footprint changed, value is no more valid.
return
$default
;
}
return
$cached_value
;
}
public
function
set
(
$key
,
$value
,
$ttl
=
null
)
{
if
(
$this
->
check_footprints
)
{
$this
->
setFootprint
(
$key
,
$value
);
}
return
parent
::
set
(
$key
,
$value
,
$ttl
);
}
public
function
delete
(
$key
)
{
if
(
$this
->
check_footprints
)
{
$this
->
setFootprint
(
$key
,
null
);
}
return
parent
::
delete
(
$key
);
}
public
function
clear
()
{
if
(
$this
->
check_footprints
)
{
$this
->
setAllCachedFootprints
([]);
}
return
parent
::
clear
();
}
public
function
getMultiple
(
$keys
,
$default
=
null
)
{
$cached_values
=
parent
::
getMultiple
(
$keys
,
$default
);
if
(
$this
->
check_footprints
)
{
foreach
(
$cached_values
as
$key
=>
$cached_value
)
{
if
(
$this
->
getCachedFootprint
(
$key
)
!==
$this
->
computeFootprint
(
$cached_value
))
{
// If footprint changed, value is no more valid.
$cached_values
[
$key
]
=
$default
;
}
}
}
return
$cached_values
;
}
public
function
setMultiple
(
$values
,
$ttl
=
null
)
{
if
(
$this
->
check_footprints
)
{
$this
->
setMultipleFootprints
(
$values
);
}
return
parent
::
setMultiple
(
$values
,
$ttl
);
}
public
function
deleteMultiple
(
$keys
)
{
if
(
$this
->
check_footprints
)
{
$values
=
array_combine
(
$keys
,
array_fill
(
0
,
count
(
$keys
),
null
));
$this
->
setMultipleFootprints
(
$values
);
}
return
parent
::
deleteMultiple
(
$keys
);
}
public
function
has
(
$key
)
{
if
(
!
parent
::
has
(
$key
))
{
return
false
;
}
if
(
!
$this
->
check_footprints
)
{
return
true
;
}
// Cache value is not usable if stale, consider it has not existing.
return
$this
->
getCachedFootprint
(
$key
)
===
$this
->
computeFootprint
(
parent
::
get
(
$key
));
}
/**
* Returns storage used for cache.
*
* @return StorageInterface
*/
public
function
getStorage
():
StorageInterface
{
return
$this
->
storage
;
}
/**
* Returns the computed footprint of a value.
*
* @param mixed $value
*
* @return string
*/
private
function
computeFootprint
(
$value
)
{
return
sha1
(
serialize
(
$value
));
}
/**
* Returns known footprint for a cached item.
*
* @param string $key
*
* @return string|null
*/
private
function
getCachedFootprint
(
$key
)
{
$footprints
=
$this
->
getAllCachedFootprints
();
return
array_key_exists
(
$key
,
$footprints
)
?
$footprints
[
$key
]
:
null
;
}
/**
* Defines footprint for cache item.
*
* @param string $key Key of the cached item.
* @param mixed $values Value of the cached item.
*
* @return void
*/
private
function
setFootprint
(
$key
,
$value
)
{
$this
->
setMultipleFootprints
([
$key
=>
$value
]);
}
/**
* Defines footprint for multiple cache items.
*
* @param array $values Associative array of cached items, where keys corresponds to the
* cache key of the item and value is its cached value.
*
* @return void
*/
private
function
setMultipleFootprints
(
array
$values
)
{
$footprints
=
$this
->
getAllCachedFootprints
();
foreach
(
$values
as
$key
=>
$value
)
{
$footprints
[
$key
]
=
$this
->
computeFootprint
(
$value
);
}
$this
->
setAllCachedFootprints
(
$footprints
);
}
/**
* Check footprint file integrity, to ensure that it can be used securely.
*
* @return void
*/
private
function
checkFootprintFileIntegrity
()
{
if
((
file_exists
(
$this
->
footprint_file
)
&&
!
is_writable
(
$this
->
footprint_file
))
||
(
!
file_exists
(
$this
->
footprint_file
)
&&
!
is_writable
(
dirname
(
$this
->
footprint_file
))))
{
trigger_error
(
sprintf
(
'Cannot write "%s" cache footprint file. Cache performance can be lowered.'
,
$this
->
footprint_file
),
E_USER_WARNING
);
$this
->
footprint_file
=
null
;
return
;
}
if
(
!
file_exists
(
$this
->
footprint_file
))
{
// Create empty array in file if not exists.
$this
->
setAllCachedFootprints
([]);
return
;
}
$file_contents
=
file_get_contents
(
$this
->
footprint_file
);
if
(
empty
(
$file_contents
))
{
// Create empty array in file if empty.
$this
->
setAllCachedFootprints
([]);
return
;
}
$footprints
=
json_decode
(
$file_contents
,
true
);
if
(
json_last_error
()
!==
JSON_ERROR_NONE
||
!
is_array
(
$footprints
))
{
// Clear footprint file if not a valid JSON.
trigger_error
(
sprintf
(
'Cache footprint file "%s" contents was invalid, it has been cleaned.'
,
$this
->
footprint_file
),
E_USER_WARNING
);
$this
->
setAllCachedFootprints
([]);
}
}
/**
* Returns all cache footprints.
*
* @return array Associative array of cached items footprints, where keys corresponds to the
* cache key of the item and value is its footprint.
*/
private
function
getAllCachedFootprints
()
{
if
(
null
!==
$this
->
footprint_file
)
{
$file_contents
=
file_get_contents
(
$this
->
footprint_file
);
$footprints
=
json_decode
(
$file_contents
,
true
);
if
(
json_last_error
()
!==
JSON_ERROR_NONE
||
!
is_array
(
$footprints
))
{
// Should happen only if file has been corrupted after cache instanciation,
// launch integrity tests again to trigger warnings and fix file contents.
$this
->
checkFootprintFileIntegrity
();
return
[];
}
return
$footprints
;
}
return
$this
->
footprint_fallback_storage
;
}
/**
* Save all cache footprints.
*
* @param array $footprints
* Associative array of cached items footprints, where keys corresponds to the
* cache key of the item and value is its footprint.
*
* @return void
*/
private
function
setAllCachedFootprints
(
$footprints
)
{
if
(
null
!==
$this
->
footprint_file
)
{
// Remove null values to prevent storage of deleted footprints
array_filter
(
$footprints
,
function
(
$val
)
{
return
null
!==
$val
;
}
);
$json
=
json_encode
(
$footprints
,
JSON_PRETTY_PRINT
);
$handle
=
fopen
(
$this
->
footprint_file
,
'c'
);
$is_locked
=
flock
(
$handle
,
LOCK_EX
);
// Lock the file, if possible (depends on used FS)
$result
=
ftruncate
(
$handle
,
0
)
&&
fwrite
(
$handle
,
$json
)
&&
fflush
(
$handle
);
if
(
$is_locked
)
{
// Unlock the file if it has been locked
flock
(
$handle
,
LOCK_UN
);
}
fclose
(
$handle
);
if
(
$result
!==
false
)
{
return
;
}
else
{
// Should happen only if file is not writable anymore (rights problems or no more disk space),
// fallback to singleton storage.
$this
->
footprint_file
=
null
;
}
}
$this
->
footprint_fallback_storage
=
$footprints
;
}
}
src/Glpi/Cache/SimpleCacheFactory.php
0 → 100644
View file @
d89dfb02
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2018 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace
Glpi\Cache
;
use
Zend\Cache\Exception\ExceptionInterface
;
/**
* Glpi simple cache factory.
*
* @since 10.0.0
*/
class
SimpleCacheFactory
{
/**
* Cache directory.
*
* @var string
*/
private
$cacheDir
;
/**
* Force skipping of integrity checks.
*
* @var bool
*/
private
$disableIntegrityChecks
;
/**
* Cache storage factory class.
*
* @var CacheStorageFactory
*/
private
$storageFactory
;
/**
* @param string $cacheDir Cache directory.
* @param bool $disableIntegrityChecks Force skipping of integrity checks.
* @param CacheStorageFactory $storageFactory Cache storage factory class.
*/
public
function
__construct
(
string
$cacheDir
,
bool
$disableIntegrityChecks
,
CacheStorageFactory
$storageFactory
)
{
$this
->
cacheDir
=
$cacheDir
;
$this
->
disableIntegrityChecks
=
$disableIntegrityChecks
;
$this
->
storageFactory
=
$storageFactory
;
}
/**
* Create a simple cache instance.
*
* @param array $cfg Cache storage configuration, see Zend\Cache\StorageFactory::factory()
*
* @return SimpleCache
*/
public
function
factory
(
$cfg
):
SimpleCache
{
$isAdapterComputed
=
!
isset
(
$cfg
[
'adapter'
])
||
'auto'
===
$cfg
[
'adapter'
];
$skipIntegrityChecks
=
$this
->
disableIntegrityChecks
||
(
!
$isAdapterComputed
&&
$this
->
canSkipIntegrityChecks
(
$cfg
[
'adapter'
]));
// Compute prefered adapter if 'auto' value or no value is used
if
(
$isAdapterComputed
)
{
if
(
function_exists
(
'wincache_ucache_add'
))
{
$cfg
[
'adapter'
]
=
'wincache'
;
}
elseif
(
function_exists
(
'apcu_fetch'
))
{
$cfg
[
'adapter'
]
=
'apcu'
;
}
else
{
$cfg
[
'adapter'
]
=
'filesystem'
;
}
}
$namespace
=
isset
(
$cfg
[
'options'
])
&&
isset
(
$cfg
[
'options'
][
'namespace'
])
?
$cfg
[
'options'
][
'namespace'
]
:
'_default'
;
try
{
$storage
=
$this
->
storageFactory
->
factory
(
$cfg
);
}
catch
(
ExceptionInterface
$e
)
{
if
(
$isAdapterComputed
&&
'filesystem'
!==
$cfg
[
'adapter'
])
{
// Fallback to 'filesystem' adapter if adapter was not explicitely defined in config
trigger_error
(
sprintf
(
'Cache adapter instantiation failed, fallback to "filesystem" adapter. Error was "%s".'
,
$e
->
getMessage
()
),
E_USER_WARNING
);
$storage
=
$this
->
storageFactory
->
factory
(
[
'adapter'
=>
'filesystem'
,
'options'
=>
[
'namespace'
=>
$namespace
.
'_fallback'
],
]
);
}
else
{
// Fallback to 'memory' adapter
trigger_error
(
sprintf
(
'Cache adapter instantiation failed, fallback to "memory" adapter. Error was "%s".'
,
$e
->
getMessage
()
),
E_USER_WARNING
);
$storage
=
$this
->
storageFactory
->
factory
(
[
'adapter'
=>
'memory'
,
'options'
=>
[
'namespace'
=>
$namespace
.
'_fallback'
],
]