!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: rccounter.gs,v 1.9 2008-01-09 22:50:13 stever Exp $
!
! Superclass Hierarchy:
!   RcCounter, Object.
!
!=========================================================================

! class created in idxclasses.topaz

removeallmethods RcCounter
removeallclassmethods RcCounter

category: 'For Documentation Installation only'
classmethod: RcCounter
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'Like any counter, an RcCounter maintains an integral value that can be
 incremented or decremented.

 A single instance of RcCounter can be shared among multiple concurrent sessions
 without conflict.  The initial value of the RcCounter at the start of a
 transaction in any session is the last value that has been committed.  During
 their transactions, any or all sessions that share the RcCounter can modify it.
 Each session then sees a value for the RcCounter that reflects only the initial
 value from the start of its own transaction and the changes made in that
 transaction.

 When a session commits the RcCounter, the cumulative changes of other
 transactions committed since the start of the current session''s transaction
 are merged with those of the committing transaction.  No commit conflicts
 occur between the sessions.  But the count that a session sees immediately
 before its transaction is committed may not be the same as the count it sees
 immediately after.' .
doc documentClassWith: txt.

self description: doc.
%

! ------------------- Class methods for RcCounter
! for 31160, increased the default number of buckets
category: 'Instance Creation'
classmethod: RcCounter
new

"Returns a new RcCounter with an initial size of 30.  The counter can handle
 at least 10 user sessions, plus the global components, and default
 system sessions."

^ super new: 30
%

category: 'Instance Creation'
classmethod: RcCounter
new: initialNumberOfUsers

"Returns a new RcCounter with a size that supports initialNumberOfUsers.  The
 new RcCounter will handle more users, but will have subcomponents created
 for initialNumberOfUsers."

| newOne |
newOne := super new: initialNumberOfUsers + 1.
^ newOne initialize
%

! ------------------- Instance methods for RcCounter
category: 'Private'
method: RcCounter
_calculateValue

"Calculates the cumulative total of all session's counter values and place it
 in the RC value cache.  Returns the total."

| total sessionElement systm |
total := 0.
systm := System .
systm _addEntireObjectToRcReadSet: self.
1 to: self size do: [ :i |
    sessionElement := self at: i.
    sessionElement ~~ nil
        ifTrue: [
            total := total + sessionElement value.
            systm _addRootObjectToRcReadSet: sessionElement
        ]
].
systm rcValueCacheAt: #value put: total for: self.
^ total
%

category: 'Support'
method: RcCounter
_getInvalidSessions

"Returns an Array of Booleans, where the value is true if the session ID at the
 corresponding index is not active."

| invalidSessions rcSize |
rcSize := self size.
invalidSessions := Array new: rcSize.
1 to: rcSize do: [ :i | invalidSessions at: i put: true ].

System currentSessions do: [ :each |
    (each < rcSize) ifTrue: [ invalidSessions at: each + 1 put: false ]
].
^ invalidSessions
%

category: 'Private'
method: RcCounter
_privateDecrementBy: aNumber

"Decrement the current session's counter value by the given amount."

| sessionElement total systm |
aNumber _validateClass: Number.
sessionElement := self _getSessionElementFor: self _thisSessionIndex.
systm := System .
systm _addRootObjectToRcReadSet: sessionElement.
sessionElement decrementValueBy: aNumber.

" if the counter total has been cached, update the cache "
(total := systm rcValueCacheAt: #value for: self otherwise: nil) == nil
    ifTrue: [ ^ self ].
System
    rcValueCacheAt: #value
    put: (total - aNumber)
    for: self
%

category: 'Private'
method: RcCounter
_privateIncrementBy: aNumber

"Increment the current session's counter value by the given amount."

| sessionElement total systm |
aNumber _validateClass: Number.
sessionElement := self _getSessionElementFor: self _thisSessionIndex.
systm := System .
systm _addRootObjectToRcReadSet: sessionElement.
sessionElement incrementValueBy: aNumber.

" if the counter total has been cached, update the cache "
(total := systm rcValueCacheAt: #value for: self otherwise: nil) == nil
    ifTrue: [ ^ self ].
System
    rcValueCacheAt: #value
    put: (total + aNumber)
    for: self
%

category: 'Support'
method: RcCounter
cleanupCounter

"For sessions that are not logged in, centralize the individual session
 element's values to the global session element (at index 1).  This may cause
 concurrency conflict if another session performs this operation."

| value invalidSessions sessionElement |
value := 0.
" get Array where unused session IDs are <true> "
invalidSessions := self _getInvalidSessions.

" set all session element's values to zero "
1 to: self size do: [ :i |
    (invalidSessions at: i)
        ifTrue: [
            sessionElement := self at: i.
            sessionElement ~~ nil
                ifTrue: [
                    value := value + sessionElement value.
                    sessionElement value: 0
                ]
        ]
].
" now set the global value at index 1 "
(self _getSessionElementFor: 1) incrementValueBy: value
%

category: 'Decrementing'
method: RcCounter
decrement

"Decrements the current session's counter value."

self _privateDecrementBy: 1
%

category: 'Decrementing'
method: RcCounter
decrementBy: aNumber

"Decrements the current session's counter value by the given amount."

self _privateDecrementBy: aNumber
%

category: 'Decrementing'
method: RcCounter
decrementBy: aNumber ifLessThan: minNumber thenExecute: aBlock

"Determine if decrementing the RcCounter by the given amount would cause the
 total value to fall below the minimum number.  If so, returns the result of
 executing the block; if not, performs the decrement and returns the receiver."

(self value - aNumber >= minNumber)
    " according to this transaction state, the decrement is valid "
    ifTrue: [ self _privateDecrementBy: aNumber ]
    ifFalse: [ ^ aBlock value ]
%

category: 'Decrementing'
method: RcCounter
decrementIfNegative: aBlock

"This is a convenience method to decrement the counter by one only if the
 counter's value does not become negative.  If it would become negative,
 execute the Block."

^ self decrementBy: 1 ifLessThan: 0 thenExecute: aBlock
%

category: 'Incrementing'
method: RcCounter
increment

"Increments the current session's counter value by 1."

self _privateIncrementBy: 1
%

category: 'Incrementing'
method: RcCounter
incrementBy: aNumber

"Increment the current session's counter value by the given amount."

self _privateIncrementBy: aNumber
%

category: 'Private'
method: RcCounter
_thisSessionIndex

"Returns the session ID + 1 (add 1 since the first RcCounter slot is reserved
 as the global counter value)."

| sessionIndex |
sessionIndex := System session + 1.
(sessionIndex > self size)
    ifTrue: [ self size: sessionIndex ].
^ sessionIndex
%

category: 'Accessing'
method: RcCounter
value

"Returns the cumulative total of all session's counter value."

| val |
val := System rcValueCacheAt: #value for: self otherwise: nil.
val == nil
    ifTrue: [ val := self _calculateValue ].
^ val
%

category: 'Private'
method: RcCounter
_getSessionElementFor: offset

"Returns the RcCounterElement for the given offset.  If one does not exist,
 then create it."

| sessionElement |
sessionElement := self _rcAt: offset.
sessionElement == nil
    ifTrue: [
        sessionElement := self class varyingConstraint new.
        sessionElement assignToSegment: self segment.
        self at: offset put: sessionElement.
        System redoLog addConflictObject: self for: self
    ].
^ sessionElement
%

category: 'Private'
method: RcCounter
_resolveRcConflictsWith: conflictObjects

"A logical write-write conflict has occurred on the receiver.  The objects that
 had the actual physical write-write conflicts are in the conflictObjects
 Array.  Returns whether the conflicts could be successfully resolved."

| offset mySessionElement |
" If no objects experienced physical conflict, then just returns "
conflictObjects isEmpty
    ifTrue: [ ^ true ].

" if only had a physical conflict on the root "
( conflictObjects size = 1 _and: [ (conflictObjects at: 1) == self ] )
    ifTrue: [
        " keep reference to this session's components "
        offset := self _thisSessionIndex.
        mySessionElement := self _rcAt: offset.
        " only abort the root "
        super _selectiveAbort.

        " get offset again to grow the root object if necessary "
        offset := self _thisSessionIndex.
        " now re-insert the session element "
        self at: offset put: mySessionElement.

        ^ true
    ].

" otherwise, selectively abort entire RcCounter and replay the operations "
^ self _abortAndReplay: conflictObjects
%

category: 'Initialization'
method: RcCounter
initialize

"Create subcomponents for all available session IDs.  This can avoid
 initial concurrency conflict when many sessions modify the RcCounter
 for the first time."

1 to: super _basicSize do: [ :i |
    self _getSessionElementFor: i
]
%

category: 'Accessing'
method: RcCounter
maxSessionId

"Returns the maximum sessionId that can be used with this RcCounter."

System _addToRcReadSet: self includingAllNodes: true.
^ super _basicSize
%

category: 'Updating'
method: RcCounter
changeToSegment: segment

"Assigns the receiver and subcomponents to the given segment."

| sessionElement |
self assignToSegment: segment.
1 to: self size do: [ :i |
    sessionElement := self at: i.
    sessionElement ~~ nil
        ifTrue: [ sessionElement assignToSegment: segment ]
]
%

