Because of this (code path) difference, adding query-related features requires implementing the logic in both places (which has led to bugs).
I propose rewriting this block of code (i.e. the query-related logic contained in QueryRpc) to process queries/annotations/etc and return data, moving it away from handleQuery. The responsibility of QueryRpc.handleQuery would then simply be to attach a formatter to the data, while the responsibility of GraphHandler.doGraph would be to turn the data into a graph (via gnuplot).
Right now, GraphHandler.doGraph (https://github.com/OpenTSDB/opentsdb/blob/08acb7374983f6b93848afb864facfbf75514247/src/tsd/GraphHandler.java#L126) implements its own query-processing logic which, among other things, fetches all series synchronously (see the TODO in https://github.com/OpenTSDB/opentsdb/blob/08acb7374983f6b93848afb864facfbf75514247/src/tsd/GraphHandler.java#L193).
QueryRpc.handleQuery has similar logic, but actually does things asynchronously (https://github.com/OpenTSDB/opentsdb/blob/08acb7374983f6b93848afb864facfbf75514247/src/tsd/QueryRpc.java#L112).
Because of this (code path) difference, adding query-related features requires implementing the logic in both places (which has led to bugs).
I propose rewriting this block of code (i.e. the query-related logic contained in QueryRpc) to process queries/annotations/etc and return data, moving it away from handleQuery. The responsibility of QueryRpc.handleQuery would then simply be to attach a formatter to the data, while the responsibility of GraphHandler.doGraph would be to turn the data into a graph (via gnuplot).