github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/tickloop.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"database/sql"
     6  	"log"
     7  	"net/http/httptest"
     8  	"strconv"
     9  	"time"
    10  
    11  	c "github.com/Azareal/Gosora/common"
    12  	"github.com/Azareal/Gosora/routes"
    13  	"github.com/Azareal/Gosora/uutils"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  var TickLoop *c.TickLoop
    18  
    19  func runHook(name string) error {
    20  	if e := c.RunTaskHook(name); e != nil {
    21  		return errors.Wrap(e, "Failed at task '"+name+"'")
    22  	}
    23  	return nil
    24  }
    25  
    26  func deferredDailies() error {
    27  	lastDailyStr, e := c.Meta.Get("lastDaily")
    28  	// TODO: Report this error back correctly...
    29  	if e != nil && e != sql.ErrNoRows {
    30  		return e
    31  	}
    32  	lastDaily, _ := strconv.ParseInt(lastDailyStr, 10, 64)
    33  	low := time.Now().Unix() - (60 * 60 * 24)
    34  	if lastDaily < low {
    35  		if e := c.Dailies(); e != nil {
    36  			return e
    37  		}
    38  	}
    39  	return nil
    40  }
    41  
    42  func handleLogLongTick(name string, cn int64, secs int) {
    43  	if !c.Dev.LogLongTick {
    44  		return
    45  	}
    46  	dur := time.Duration(uutils.Nanotime() - cn)
    47  	if dur.Seconds() > float64(secs) {
    48  		log.Print("tick " + name + " completed in " + dur.String())
    49  	}
    50  }
    51  
    52  func tickLoop(thumbChan chan bool) error {
    53  	tl := c.NewTickLoop()
    54  	TickLoop = tl
    55  	if e := deferredDailies(); e != nil {
    56  		return e
    57  	}
    58  	if e := c.StartupTasks(); e != nil {
    59  		return e
    60  	}
    61  
    62  	startTick := func(ch chan bool) (ret bool) {
    63  		if c.Dev.HourDBTimeout {
    64  			go func() {
    65  				defer c.EatPanics()
    66  				ch <- c.StartTick()
    67  			}()
    68  			return <-ch
    69  		}
    70  		return c.StartTick()
    71  	}
    72  	tick := func(name string, tasks c.TaskSet, secs int) error {
    73  		tw := c.NewTickWatch()
    74  		tw.Name = name
    75  		tw.Set(&tw.Start, uutils.Nanotime())
    76  		tw.Run()
    77  		defer tw.Stop()
    78  		ch := make(chan bool)
    79  		tw.OutEndChan = ch
    80  		if startTick(ch) {
    81  			return nil
    82  		}
    83  		tw.Set(&tw.DBCheck, uutils.Nanotime())
    84  		if e := runHook("before_" + name + "_tick"); e != nil {
    85  			return e
    86  		}
    87  		cn := uutils.Nanotime()
    88  		tw.Set(&tw.StartHook, cn)
    89  		if e := tasks.Run(); e != nil {
    90  			return e
    91  		}
    92  		tw.Set(&tw.Tasks, uutils.Nanotime())
    93  		handleLogLongTick(name, cn, secs)
    94  		if e := runHook("after_" + name + "_tick"); e != nil {
    95  			return e
    96  		}
    97  		tw.Set(&tw.EndHook, uutils.Nanotime())
    98  		//close(tw.OutEndChan)
    99  		return nil
   100  	}
   101  
   102  	tl.HalfSecf = func() error {
   103  		return tick("half_second", c.Tasks.HalfSec, 2)
   104  	}
   105  	// TODO: Automatically lock topics, if they're really old, and the associated setting is enabled.
   106  	// TODO: Publish scheduled posts.
   107  	tl.FifteenMinf = func() error {
   108  		return tick("fifteen_minute", c.Tasks.FifteenMin, 5)
   109  	}
   110  	// TODO: Handle the instance going down a lot better
   111  	// TODO: Handle the daily clean-up.
   112  	tl.Dayf = func() error {
   113  		if c.StartTick() {
   114  			return nil
   115  		}
   116  		cn := uutils.Nanotime()
   117  		if e := c.Dailies(); e != nil {
   118  			return e
   119  		}
   120  		handleLogLongTick("day", cn, 5)
   121  		return nil
   122  	}
   123  
   124  	tl.Secf = func() (e error) {
   125  		if c.StartTick() {
   126  			return nil
   127  		}
   128  		if e = runHook("before_second_tick"); e != nil {
   129  			return e
   130  		}
   131  		cn := uutils.Nanotime()
   132  		go func() {
   133  			defer c.EatPanics()
   134  			thumbChan <- true
   135  		}()
   136  
   137  		if e = c.Tasks.Sec.Run(); e != nil {
   138  			return e
   139  		}
   140  
   141  		// TODO: Stop hard-coding this
   142  		if e = c.HandleExpiredScheduledGroups(); e != nil {
   143  			return e
   144  		}
   145  
   146  		// TODO: Handle delayed moderation tasks
   147  
   148  		// Sync with the database, if there are any changes
   149  		if e = c.HandleServerSync(); e != nil {
   150  			return e
   151  		}
   152  		handleLogLongTick("second", cn, 3)
   153  
   154  		// TODO: Manage the TopicStore, UserStore, and ForumStore
   155  		// TODO: Alert the admin, if CPU usage, RAM usage, or the number of posts in the past second are too high
   156  		// TODO: Clean-up alerts with no unread matches which are over two weeks old. Move this to a 24 hour task?
   157  		// TODO: Rescan the static files for changes
   158  		return runHook("after_second_tick")
   159  	}
   160  
   161  	tl.Hourf = func() error {
   162  		if c.StartTick() {
   163  			return nil
   164  		}
   165  		if e := runHook("before_hour_tick"); e != nil {
   166  			return e
   167  		}
   168  		cn := uutils.Nanotime()
   169  
   170  		jsToken, e := c.GenerateSafeString(80)
   171  		if e != nil {
   172  			return e
   173  		}
   174  		c.JSTokenBox.Store(jsToken)
   175  
   176  		c.OldSessionSigningKeyBox.Store(c.SessionSigningKeyBox.Load().(string)) // TODO: We probably don't need this type conversion
   177  		sessionSigningKey, e := c.GenerateSafeString(80)
   178  		if e != nil {
   179  			return e
   180  		}
   181  		c.SessionSigningKeyBox.Store(sessionSigningKey)
   182  
   183  		if e = c.Tasks.Hour.Run(); e != nil {
   184  			return e
   185  		}
   186  		if e = PingLastTopicTick(); e != nil {
   187  			return e
   188  		}
   189  		handleLogLongTick("hour", cn, 5)
   190  		return runHook("after_hour_tick")
   191  	}
   192  
   193  	c.CTickLoop = tl
   194  	return nil
   195  }
   196  
   197  func sched() error {
   198  	ws := errors.WithStack
   199  	schedStr, err := c.Meta.Get("sched")
   200  	// TODO: Report this error back correctly...
   201  	if err != nil && err != sql.ErrNoRows {
   202  		return ws(err)
   203  	}
   204  
   205  	if schedStr == "recalc" {
   206  		log.Print("Cleaning up orphaned data.")
   207  
   208  		count, err := c.Recalc.Replies()
   209  		if err != nil {
   210  			return ws(err)
   211  		}
   212  		log.Printf("Deleted %d orphaned replies.", count)
   213  
   214  		count, err = c.Recalc.Forums()
   215  		if err != nil {
   216  			return ws(err)
   217  		}
   218  		log.Printf("Recalculated %d forum topic counts.", count)
   219  
   220  		count, err = c.Recalc.Subscriptions()
   221  		if err != nil {
   222  			return ws(err)
   223  		}
   224  		log.Printf("Deleted %d orphaned subscriptions.", count)
   225  
   226  		count, err = c.Recalc.ActivityStream()
   227  		if err != nil {
   228  			return ws(err)
   229  		}
   230  		log.Printf("Deleted %d orphaned activity stream items.", count)
   231  
   232  		err = c.Recalc.Users()
   233  		if err != nil {
   234  			return ws(err)
   235  		}
   236  		log.Print("Recalculated user post stats.")
   237  
   238  		count, err = c.Recalc.Attachments()
   239  		if err != nil {
   240  			return ws(err)
   241  		}
   242  		log.Printf("Deleted %d orphaned attachments.", count)
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  var pingLastTopicCount = 1
   249  
   250  // TODO: Move somewhere else
   251  func PingLastTopicTick() error {
   252  	g, e := c.Groups.Get(c.GuestUser.Group)
   253  	if e != nil {
   254  		return e
   255  	}
   256  	tList, _, _, e := c.TopicList.GetListByGroup(g, 1, 0, nil)
   257  	if e != nil {
   258  		return e
   259  	}
   260  	if len(tList) == 0 {
   261  		return nil
   262  	}
   263  	w := httptest.NewRecorder()
   264  	sid := strconv.Itoa(tList[0].ID)
   265  	req := httptest.NewRequest("get", "/topic/"+sid, bytes.NewReader(nil))
   266  	cn := uutils.Nanotime()
   267  
   268  	// Deal with the session stuff, etc.
   269  	ucpy, ok := c.PreRoute(w, req)
   270  	if !ok {
   271  		return errors.New("preroute failed")
   272  	}
   273  	head, rerr := c.UserCheck(w, req, &ucpy)
   274  	if rerr != nil {
   275  		return errors.New(rerr.Error())
   276  	}
   277  	rerr = routes.ViewTopic(w, req, &ucpy, head, sid)
   278  	if rerr != nil {
   279  		return errors.New(rerr.Error())
   280  	}
   281  	/*if w.Code != 200 {
   282  		return errors.New("topic code not 200")
   283  	}*/
   284  
   285  	dur := time.Duration(uutils.Nanotime() - cn)
   286  	if dur.Seconds() > 5 {
   287  		c.Log("topic " + sid + " completed in " + dur.String())
   288  	} else if c.Dev.Log4thLongRoute {
   289  		pingLastTopicCount++
   290  		if pingLastTopicCount == 4 {
   291  			c.Log("topic " + sid + " completed in " + dur.String())
   292  		}
   293  		if pingLastTopicCount >= 4 {
   294  			pingLastTopicCount = 1
   295  		}
   296  	}
   297  
   298  	return nil
   299  }