Index: openacs-4/packages/acs-tcl/tcl/acs-cache-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/acs-cache-procs.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-tcl/tcl/acs-cache-procs.tcl 26 Jun 2018 09:10:44 -0000 1.1 @@ -0,0 +1,400 @@ +# +# Copyright (C) 2018 Gustaf Neumann, neumann@wu-wien.ac.at +# +# Vienna University of Economics and Business +# Institute of Information Systems and New Media +# A-1020, Welthandelsplatz 1 +# Vienna, Austria +# +# This is a BSD-Style license applicable for this file. +# +# Permission to use, copy, modify, distribute, and sell this +# software and its documentation for any purpose is hereby granted +# without fee, provided that the above copyright notice appear in +# all copies and that both that copyright notice and this permission +# notice appear in supporting documentation. We make no +# representations about the suitability of this software for any +# purpose. It is provided "as is" without express or implied +# warranty. +# + +namespace eval ::acs { + + ########################################################################## + # + # Generic Cache class + # + ########################################################################## + + nx::Class create ::acs::Cache { + # + # Provide a base class to generalize cache management to + # extend cache primitiva like e.g. for cache partitioning. + # + :property parameter:required + :property package_key:required + :property maxentry:integer + :property {default_size:integer 10000} + + :method cache_name {key} { + # + # More or less dummy function, which can be refined. The + # base definition completely ignores "key". + # + return ${:name} + } + + :method get_size {} { + # + # Determine the cache size depending on configuration + # variables. + # + return [::parameter::get_from_package_key \ + -package_key ${:package_key} \ + -parameter "${:parameter}Size" \ + -default ${:default_size}] + } + + :public method flush {{-partition_key} key} { + if {![info exists partition_key]} {set partition_key $key} + ::acs::clusterwide ns_cache flush [:cache_name $partition_key] $key + } + + if {[info commands ns_cache_eval] ne ""} { + # + # NaviServer variant + # + :public method eval {{-partition_key} key command} { + # + # Evaluate the command unless it is cached. + # + if {![info exists partition_key]} {set partition_key $key} + try { + :uplevel [list ns_cache_eval -- [:cache_name $partition_key] $key $command] + + } on break {r} { + # + # When the command ends with "break", it means: + # "don't cache". We return in this case always a + # 0. + # + #ns_log notice "====================== [self] $key -> break -> <$r>" + return 0 + + } on ok {r} { + return $r + } + } + + :public method set {key value} { + # + # Set some value in the cache. This code uses + # ns_cache_eval to achieve this behavior, which is + # typically a AOLserver idom and should be avaoided. + # + if {![info exists partition_key]} {set partition_key $key} + :uplevel [list ns_cache_eval -force -- [:cache_name $partition_key] $key [list set _ $value]] + } + + :public method flush_pattern {{-partition_key ""} pattern} { + # + # Flush in the cache a value based on a pattern + # operation. Use this function rarely, since on large + # caches (e.g. 100k entries or more) the glob + # operation will cause long locks, which should be + # avoided. The partitioned variants can help to reduce + # the lock times. + # + return [ns_cache_flush -glob [:cache_name $partition_key] $pattern] + } + + :method cache_create {name size} { + # + # Create a cache. + # + ns_cache_create \ + {*}[expr {[info exists :maxentry] ? "-maxentry ${:maxentry}" : ""}] \ + $name $size + } + + } else { + # + # AOLserver variant + # + :public method eval {{-partition_key} key body} { + if {![info exists partition_key]} {set partition_key $key} + try { + :uplevel [list ns_cache eval [:cache_name $partition_key] $key $body] + } on break {r} { + return 0 + } on ok {r} { + return $r + } + } + :public method set {{-partition_key} key value} { + if {![info exists partition_key]} {set partition_key $key} + :uplevel [list ns_cache set [:cache_name $partition_key] $key $value] + } + :public method flush_pattern {{-partition_key ""} pattern} { + foreach name [ns_cache names [:cache_name $partition_key] $pattern] { + :flush -partition_key $partition_key $name + } + } + :public method flush_cache {{-partition_key ""}} { + ns_cache_flush [:cache_name $partition_key] + } + :method cache_create {name size} { + ns_cache create $name -size $size + } + } + + :public method flush_cache {{-partition_key ""}} { + # + # Flush all entries in a cache. Both, NaviServer and + # AOLserver support "ns_cache_flush". + # + ns_cache_flush [:cache_name $partition_key] + } + + :public method flush_all {} { + # + # Flush all contents of all (partitioned) caches. In the + # case of a base ::acs::Cache, it is identical to + # "flush_cash". + # + :flush_cache + } + + :public method init {} { + set :name [namespace tail [current]] + :cache_create ${:name} [:get_size] + } + } + + ########################################################################## + # + # Simple Partitioned Cache class + # + # Partitioning is based on a modulo function using the provided + # key, which has to be numeric. So far, no partitioning-spanning + # methods are provided. + # + ########################################################################## + + nx::Class create ::acs::PartitionedCache -superclasses ::acs::Cache { + :property {partitions:integer 1} + + :protected method cache_name {key:integer} { + return ${:name}-[expr {$key % ${:partitions}}] + } + + :public method init {} { + set :name [namespace tail [current]] + set partitions [::parameter::get_from_package_key \ + -package_key ${:package_key} \ + -parameter "${:parameter}Partitions" \ + -default ${:partitions}] + # + # Create multiple separate caches depending on the + # partitions. A PartitionedCache requires to have a + # partitioning function that determines the nth partition + # number from some partition_key. + # + set size [expr {[:get_size] / ${:partitions}}] + for {set i 0} {$i < ${:partitions}} {incr i} { + :cache_create ${:name}-$i $size + } + } + + :public method flush_all {{-partition_key ""}} { + # + # Flush all entries in all caches. Both, NaviServer and + # AOLserver support "ns_cache_flush". + # + for {set i 0} {$i < ${:partitions}} {incr i} { + ns_cache_flush ${:name}-$i + } + } + } + + + ########################################################################## + # + # Tree Partitioned Cache class + # + # Tree Partitioning is based on a modulo function using a special + # partition_key, which has to be numeric. So far, no + # partitioning-spanning methods are provided. + # + ########################################################################## + + nx::Class create ::acs::TreePartitionedCache -superclasses ::acs::PartitionedCache { + :property {partitions:integer 1} + + :public method flush_pattern {{-partition_key:integer,required} pattern} { + # + # flush just in the determined partition + # + next + } + + :public method flush {{-partition_key:integer,required} key} { + next + } + + :public method set {{-partition_key:integer,required} key value} { + next + } + } +} + + +namespace eval ::acs { + ########################################################################## + # + # Cluster Management + # + # If a site is running a cluster of OpenACS systems, certain + # commands have to be executed on cluster nodes (e.g. flushing + # caches, etc). A cluster setup is currently not commonly used and + # requires probably some work, but the code here provides a basic + # infrastructure. It is a good practice to flag commands to be + # executed on all clusternodes in the code with + # ::acs::clusterwide. + ########################################################################## + + proc clusterwide args { + # + # First, execute the command on the local server. + # + eval $args + # + # Then, distribute the command to all servers in the cluster. + # + ::acs::Cluster broadcast {*}$args + } + + proc cache_flush_all {cache pattern} { + # Provide means to perform a wildcard-based cache flushing on + # (cluster) machines. + foreach n [ns_cache names $cache $pattern] {ns_cache flush $cache $n} + } + + nx::Class create Cluster { + :property host + :property {port 80} + + set :allowed_host_patterns [list] + set :url /acs-cluster-do + array set :allowed_host { "127.0.0.1" 1 } + + # + # The allowed commands are of the form + # - command names followed by + # - optional "except patterns" + # + set :allowed_command { + set "" + unset "" + nsv_set "" + nsv_unset "" + nsv_incr "" + bgdelivery "" + ns_cache "^ns_cache\s+eval" + ns_cache_flush "" + acs::cache_flush_all "" + } + + # + # handling the ns_filter methods + # + :public object method trace args { + :log "" + return filter_return + } + + :public object method preauth args { + :log "" + :incoming_request + return filter_return + } + + :public object method postauth args { + :log "" + return filter_return + } + + # + # handle incoming request issues + # + :public object method incoming_request {} { + set cmd [ns_queryget cmd] + set addr [lindex [ns_set iget [ns_conn headers] x-forwarded-for] end] + if {$addr eq ""} {set addr [ns_conn peeraddr]} + #ns_log notice "--cluster got cmd='$cmd' from $addr" + ad_try { + set result [::acs::Cluster execute [ns_conn peeraddr] $cmd] + } on error {errorMsg} { + ns_log notice "--cluster error: $errorMsg" + ns_return 417 text/plain $errorMsg + } on ok {r} { + #ns_log notice "--cluster success $result" + ns_return 200 text/plain $result + } + } + + # + # Handling outgoing requests + # + :public object method execute {host cmd} { + if {![info exists :allowed_host($host)]} { + set ok 0 + foreach g ${:allowed_host_patterns} { + if {[string match $g $host]} { + set ok 1 + break + } + } + if {!$ok} { + error "refuse to execute commands from $host (command: '$cmd')" + } + } + set cmd_name [lindex $cmd 0] + set key allowed_command($cmd_name) + #ns_log notice "--cluster $key exists ? [info exists :$key]" + if {[info exists :$key]} { + set except_RE [set :$key] + #ns_log notice "--cluster [list regexp $except_RE $cmd] -> [regexp $except_RE $cmd]" + if {$except_RE eq "" || ![regexp $except_RE $cmd]} { + ns_log notice "--cluster executes command '$cmd' from host $host" + return [eval $cmd] + } + } + error "command '$cmd' from host $host not allowed" + } + + # + # Handling outgoing requests + # + :public object method broadcast args { + foreach server [:info instances] { + $server message {*}$args + } + } + + :public object method message args { + :log "--cluster outgoing request to [:host]:[:port] // $args" + + utl::http::get -url http://[:host]:[:port]/[:url]?cmd=[ns_urlencode $args] + } + } +} + + + +# Local variables: +# mode: tcl +# tcl-indent-level: 4 +# indent-tabs-mode: nil +# End: + Index: openacs-4/packages/acs-tcl/tcl/cluster-init.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/cluster-init.tcl,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-tcl/tcl/cluster-init.tcl 26 Jun 2018 09:10:44 -0000 1.1 @@ -0,0 +1,51 @@ +# +# Check if cluster is enabled, and if, set up the custer objects +# +if {[server_cluster_enabled_p]} { + set my_ip [ns_config ns/server/[ns_info server]/module/nssock Address] + set my_port [ns_config ns/server/[ns_info server]/module/nssock port] + + foreach host [server_cluster_all_hosts] { + set port 80 + regexp {^(.*):(.*)} $host _ host port + if {"$host-$port" eq "$my_ip-$my_port"} continue + ::acs::Cluster create CS_${host}_$port -host $host -port $port + } + + foreach ip [parameter::get -package_id [ad_acs_kernel_id] -parameter ClusterAuthorizedIP] { + if {[string first * $ip] > -1} { + ::acs::Cluster eval [subst { + :lappend allowed_host_patterns $ip + }] + } else { + ::acs::Cluster eval [subst { + set :allowed_host($ip) 1 + }] + } + } + + set url [::acs::Cluster eval {set :url}] + + # Check, if the filter url mirrors a site node. If so, + # the cluster mechanism will not work, if the site node + # requires a login. Clustering will only work if the + # root node is freely accessible. + + array set node [site_node::get -url $url] + if {$node(url) ne "/"} { + ns_log notice "***\n*** WARNING: there appears a package mounted on\ + $url\n***Cluster configuration will not work\ + since there is a conflict with the AOLserver filter with the same name!\n" + } + + #ns_register_filter trace GET $url ::acs::Cluster + ns_register_filter preauth GET $url ::acs::Cluster + #ad_register_filter -priority 900 preauth GET $url ::acs::Cluster +} + +# +# Local variables: +# mode: tcl +# tcl-indent-level: 4 +# indent-tabs-mode: nil +# End: Index: openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl,v diff -u -r1.110 -r1.111 --- openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl 11 Jun 2018 09:14:55 -0000 1.110 +++ openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl 26 Jun 2018 09:10:44 -0000 1.111 @@ -107,8 +107,9 @@ set node_id [package_instantiate_object -var_list $var_list site_node] - #Now update the nsv caches. We don't need to update the object_id and package_key caches - #because nothing is mounted here yet. + # Now update the nsv caches. We don't need to update the + # object_id and package_key caches because nothing is mounted here + # yet. # Grab the lock so our URL key doesn't change on us midstream ns_mutex lock [nsv_get site_nodes_mutex mutex] @@ -1120,7 +1121,7 @@ # # the following query is just for PG, TODO: Oracle is missing # - set child_urls [::xo::dc list [current method]-all { + set child_urls [::xo::dc list -prepare integer [current method]-all { select site_node__url(children.node_id) from site_nodes as parent, site_nodes as children where parent.node_id = :node_id @@ -1208,7 +1209,7 @@ # # the following query is just for PG, TODO: Oracle is missing # - set child_urls [::xo::dc list [current method]-all { + set child_urls [::xo::dc list -prepare integer [current method]-all { select site_node__url(node_id) from site_nodes where object_id = :object_id @@ -1219,7 +1220,7 @@ :public method get_urls_from_package_key { -package_key:required } { - return [::xo::dc list [current method]-urls-from-package-key { + return [::xo::dc list -prepare varchar [current method]-urls-from-package-key { select site_node__url(node_id) from site_nodes n, apm_packages p where p.package_key = :package_key @@ -1272,9 +1273,9 @@ ##################################################### if {[info commands ::ns_cache_names] ne ""} { - set createCache [expr {"xo_site_nodes" ni [::ns_cache_names]}] + set createCache [expr {"site_nodes_cache" ni [::ns_cache_names]}] } else { - set createCache [catch {ns_cache flush xo_site_nodes NOTHING}] + set createCache [catch {ns_cache flush site_nodes_cache NOTHING}] } if {$createCache} { # @@ -1285,28 +1286,31 @@ # ns_param SiteNodesCacheSize 2000000 # ns_param SiteNodesIdCacheSize 100000 # ns_param SiteNodesChildenCacheSize 100000 + # - foreach {cache parameter default} { - xo_site_nodes SiteNodesCacheSize 2000000 - xo_site_nodes_id SiteNodesIdCacheSize 100000 - xo_site_nodes_children SiteNodesChildenCacheSize 100000 - } { - set size [parameter::get_from_package_key \ - -package_key acs-tcl \ - -parameter $parameter \ - -default $default] - ns_log notice "site-nodes: create cache $cache -size $size" - ns_cache create $cache -size $size - } + ::acs::TreePartitionedCache create ::acs::site_nodes_cache \ + -package_key acs-tcl \ + -parameter SiteNodesCache \ + -default_size 2000000 + + ::acs::Cache create ::acs::site_nodes_id_cache \ + -package_key acs-tcl \ + -parameter SiteNodesIdCache \ + -default_size 100000 + + ::acs::TreePartitionedCache create ::acs::site_nodes_children_cache \ + -package_key acs-tcl \ + -parameter SiteNodesChildenCache \ + -default_size 100000 } # - # SiteNodeCache is a mixin class for caching the SiteNode objects. + # SiteNodesCache is a mixin class for caching the SiteNode objects. # Add/remove caching methods as wanted. Removing the registry of # the object mixin deactivates caching for these methods # completely. # - ::nx::Class create SiteNodeCache { + ::nx::Class create SiteNodesCache { :public method get_children { -node_id:required @@ -1323,53 +1327,48 @@ # next } else { - ns_cache_eval xo_site_nodes_children \ + ::acs::site_nodes_children_cache eval -partition_key $node_id \ get_children-$node_id-$all-$package_type-$package_key-$filters-$element { next } } } :public method get_node_id {-url:required} { - ns_cache_eval xo_site_nodes_id id-$url { next } + acs::site_nodes_id_cache eval id-$url { next } } :protected method properties {-node_id:required} { - set key ::xo_site_nodes_property($node_id) + set key ::__site_nodes_property($node_id) if {[info exists $key]} { return [set $key] } - set $key [ns_cache_eval xo_site_nodes p-$node_id { next }] + set $key [::acs::site_nodes_cache eval -partition_key $node_id $node_id { next }] return [set $key] } :public method get_url {-node_id:required} { - ns_cache_eval xo_site_nodes url-$node_id { next } + ::acs::site_nodes_cache eval -partition_key $node_id url-$node_id { next } } :public method get_urls_from_object_id {-object_id:required} { - ns_cache_eval xo_site_nodes urls-$object_id { next } + ::acs::site_nodes_cache eval -partition_key $object_id urls-$object_id { next } } # The cache value from the following method is currently not # flushed, but just used for package keys, not instances, so it # should be safe. :public method get_package_url {-package_key:required} { - ns_cache_eval xo_site_nodes package_url-$package_key { next } + ::acs::site_nodes_cache eval -partition_key 0 package_url-$package_key { next } } - :public method flush_all {patterns} { - foreach pattern $patterns { - switch -glob -- $pattern { - id-* {set cache xo_site_nodes_id} - get_children-* {set cache xo_site_nodes_children} - default {set cache xo_site_nodes} - } - foreach key [ns_cache names $cache $pattern] { - #:msg ......key=$key - ::xo::clusterwide ns_cache flush $cache $key - } + :public method flush_pattern {{-partition_key ""} pattern} { + switch -glob -- $pattern { + id-* {set cache site_nodes_id_cache} + get_children-* {set cache site_nodes_children_cache} + default {set cache site_nodes_cache} } + ::acs::$cache flush_pattern -partition_key $partition_key $pattern } :public method flush_cache {{-node_id ""} {-with_subtree:boolean true} {-url ""}} { @@ -1384,15 +1383,15 @@ set old_url [:get_url -node_id $node_id] if {$node_id eq "" || $old_url eq "/"} { - ::xo::clusterwide ns_cache_flush xo_site_nodes - ::xo::clusterwide ns_cache_flush xo_site_nodes_id - ::xo::clusterwide ns_cache_flush xo_site_nodes_children + ::acs::site_nodes_cache flush_cache + ::acs::site_nodes_id_cache flush_cache + ::acs::site_nodes_children_cache flush_cache } else { set limit_clause [expr {$with_subtree ? "" : "limit 1"}] # # The following query is just for PG, TODO: Oracle is missing # - set tree [::xo::dc list_of_lists [current method]-flush-tree [subst { + set tree [::xo::dc list_of_lists -prepare integer [current method]-flush-tree [subst { select site_node__url(children.node_id), children.node_id, children.object_id from site_nodes as parent, site_nodes as children where parent.node_id = :node_id @@ -1402,16 +1401,16 @@ foreach entry $tree { lassign $entry url node_id object_id foreach key [list p-$node_id url-$node_id urls-$object_id] { - ::xo::clusterwide ns_cache flush xo_site_nodes $key + ::acs::site_nodes_cache flush -partition_key $node_id $key } - :flush_all get_children-$node_id-* + :flush_pattern -partition_key $node_id get_children-$node_id-* } regsub {/$} $old_url "" old_url - :flush_all id-$old_url* + :flush_pattern id-$old_url* } } } - + ::nx::Class create SiteNodeUrlspaceCache { # # Cache site-node information via ns_urlspace. We can use @@ -1452,10 +1451,10 @@ } else { # # Get value the classical way, caching potentially - # the full url path in the xo_site_nodes_id cache. + # the full url path in the site_nodes_id_cache. # set ID [next] - #ns_log notice "--- get_node_id from xo_site_nodes_id <$url> -> <$ID>" + #ns_log notice "--- get_node_id from site_nodes_id_cache <$url> -> <$ID>" if {$ID ne ""} { # # We got a valid ID. If we would add blindly a @@ -1480,29 +1479,29 @@ {*}$cmd #ns_log notice "---\n[join [ns_urlspace list] \n]" } - return [set $key $ID] + return [set $key $ID] } } } - + :public method flush_cache {{-node_id ""} {-with_subtree:boolean true} {-url ""}} { # # Cleanup in the urspace tree: Clear always the # full subtree via "-recurse" (maybe not always # necessary). # - + #ns_log notice ==========flush_cache=================[list ns_urlspace unset -recurse -key sitenode $url] ns_urlspace unset -recurse -key sitenode $url - + next } } - site_node object mixins add SiteNodeCache + site_node object mixins add SiteNodesCache if {[info commands ns_urlspace] ne ""} { - ns_log notice "... using NaviServer's ns_urlspace for reduced redundancy in site node caches" + ns_log notice "... using NaviServer's ns_urlspace for reduced redundancy in site node caches" site_node object mixins add SiteNodeUrlspaceCache } @@ -1560,9 +1559,9 @@ # set parent_node_id [site_node::get_parent_id -node_id [site_node::get_parent_id -node_id $node_id]] set url [site_node::get_url -node_id $parent_node_id] - + site_node::update_cache -sync_children -node_id $node_id -url $url - ::xo::site_node flush_all get_children-$parent_node_id-* + ::acs::site_nodes_cache flush_pattern -partition_key $parent_node_id get_children-$parent_node_id-* # DAVEB update context_id if it is passed in some code relies # on context_id to be set by instantiate_and_mount so we can't @@ -1610,7 +1609,7 @@ ::xo::site_node flush_cache -node_id $node_id -with_subtree $sync_children_p -url $url set parent_node_id [site_node::get_parent_id -node_id $node_id] - ::xo::site_node flush_all get_children-$parent_node_id-* + ::xo::site_node flush_pattern -partition_key $parent_node_id get_children-$parent_node_id-* } ad_proc -public site_node::get {